From 4f73fc56157b70cf4f03545d1713fee789b8ceb4 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Tue, 15 Mar 2005 01:56:24 +0000 Subject: [PATCH] Bring in the minix editor 'mined', in its original form except for necessary DragonFly CVS IDs at the top of each file. Bring the LICENSE file in from http://www.cs.vu.nl/pub/minix/LICENSE. --- bin/mined/LICENSE | 39 + bin/mined/Makefile | 8 + bin/mined/mined.h | 375 +++++++++ bin/mined/mined1.c | 1988 ++++++++++++++++++++++++++++++++++++++++++++ bin/mined/mined2.c | 1725 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 4135 insertions(+) create mode 100644 bin/mined/LICENSE create mode 100644 bin/mined/Makefile create mode 100644 bin/mined/mined.h create mode 100644 bin/mined/mined1.c create mode 100644 bin/mined/mined2.c diff --git a/bin/mined/LICENSE b/bin/mined/LICENSE new file mode 100644 index 0000000000..9339efaf07 --- /dev/null +++ b/bin/mined/LICENSE @@ -0,0 +1,39 @@ + +[ this license file was fetched from http://www.cs.vu.nl/pub/minix/LICENSE + on 14 March 2005 and applies to mined.h, mined1.c, and mined2.c ] +$DragonFly: src/bin/mined/LICENSE,v 1.1 2005/03/15 01:56:24 dillon Exp $ + +----- + + Copyright (c) 1987,1997, Prentice Hall + All rights reserved. + + Redistribution and use of the MINIX operating system in source and + binary forms, with or without modification, are permitted provided + that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Prentice Hall nor the names of the software + authors or contributors may be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS, AUTHORS, AND + CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL PRENTICE HALL OR ANY AUTHORS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/bin/mined/Makefile b/bin/mined/Makefile new file mode 100644 index 0000000000..feec8b1e30 --- /dev/null +++ b/bin/mined/Makefile @@ -0,0 +1,8 @@ +# $DragonFly: src/bin/mined/Makefile,v 1.1 2005/03/15 01:56:24 dillon Exp $ +# + +PROG= mined +SRCS= mined1.c mined2.c +WARNS=0 + +.include diff --git a/bin/mined/mined.h b/bin/mined/mined.h new file mode 100644 index 0000000000..5123d8d9de --- /dev/null +++ b/bin/mined/mined.h @@ -0,0 +1,375 @@ +/* $DragonFly: src/bin/mined/mined.h,v 1.1 2005/03/15 01:56:24 dillon Exp $ */ +/*========================================================================* + * Mined.h * + *========================================================================*/ + +#define _PROTOTYPE(a, b) a b + +#include +#include +#include +#include +#include + +#ifndef YMAX +#ifdef UNIX +#include +#undef putchar +#undef getchar +#undef NULL +#undef EOF +extern char *CE, *VS, *SO, *SE, *CL, *AL, *CM; +#define YMAX 49 +#else +#define YMAX 24 /* Maximum y coordinate starting at 0 */ +/* Escape sequences. */ +extern char *enter_string; /* String printed on entering mined */ +extern char *rev_video; /* String for starting reverse video */ +extern char *normal_video; /* String for leaving reverse video */ +extern char *rev_scroll; /* String for reverse scrolling */ +extern char *pos_string; /* Absolute cursor positioning */ +#define X_PLUS ' ' /* To be added to x for cursor sequence */ +#define Y_PLUS ' ' /* To be added to y for cursor sequence */ +#endif /* UNIX */ + +#define XMAX 79 /* Maximum x coordinate starting at 0*/ +#define SCREENMAX (YMAX - 1) /* Number of lines displayed */ +#define XBREAK (XMAX - 0) /* Line shift at this coordinate */ +#define SHIFT_SIZE 25 /* Number of chars to shift */ +#define SHIFT_MARK '!' /* Char indicating line continues */ +#define MAX_CHARS 1024 /* Maximum chars on one line */ + +/* LINE_START must be rounded up to the lowest SHIFT_SIZE */ +#define LINE_START (((-MAX_CHARS - 1) / SHIFT_SIZE) * SHIFT_SIZE \ + - SHIFT_SIZE) +#define LINE_END (MAX_CHARS + 1) /* Highest x-coordinate for line */ + +#define LINE_LEN (XMAX + 1) /* Number of characters on line */ +#define SCREEN_SIZE (XMAX * YMAX) /* Size of I/O buffering */ +#define BLOCK_SIZE 1024 + +/* Return values of functions */ +#define ERRORS -1 +#define NO_LINE (ERRORS - 1) /* Must be < 0 */ +#define FINE (ERRORS + 1) +#define NO_INPUT (ERRORS + 2) + +#define STD_OUT 1 /* File descriptor for terminal */ + +#if (CHIP == INTEL) +#define MEMORY_SIZE (50 * 1024) /* Size of data space to malloc */ +#endif + +#define REPORT 2 /* Report change of lines on # lines */ + +typedef int FLAG; + +/* General flags */ +#define FALSE 0 +#define TRUE 1 +#define NOT_VALID 2 +#define VALID 3 +#define OFF 4 +#define ON 5 + +/* Expression flags */ +#define FORWARD 6 +#define REVERSE 7 + +/* Yank flags */ +#define SMALLER 8 +#define BIGGER 9 +#define SAME 10 +#define EMPTY 11 +#define NO_DELETE 12 +#define DELETE 13 +#define READ 14 +#define WRITE 15 + +/* + * The Line structure. Each line entry contains a pointer to the next line, + * a pointer to the previous line, a pointer to the text and an unsigned char + * telling at which offset of the line printing should start (usually 0). + */ +struct Line { + struct Line *next; + struct Line *prev; + char *text; + unsigned char shift_count; +}; + +typedef struct Line LINE; + +/* Dummy line indicator */ +#define DUMMY 0x80 +#define DUMMY_MASK 0x7F + +/* Expression definitions */ +#define NO_MATCH 0 +#define MATCH 1 +#define REG_ERROR 2 + +#define BEGIN_LINE (2 * REG_ERROR) +#define END_LINE (2 * BEGIN_LINE) + +/* + * The regex structure. Status can be any of 0, BEGIN_LINE or REG_ERROR. In + * the last case, the result.err_mess field is assigned. Start_ptr and end_ptr + * point to the match found. For more details see the documentation file. + */ +struct regex { + union { + char *err_mess; + int *expression; + } result; + char status; + char *start_ptr; + char *end_ptr; +}; + +typedef struct regex REGEX; + +/* NULL definitions */ +#define NIL_PTR ((char *) 0) +#define NIL_LINE ((LINE *) 0) +#define NIL_REG ((REGEX *) 0) +#define NIL_INT ((int *) 0) + +/* + * Forward declarations + */ +extern int nlines; /* Number of lines in file */ +extern LINE *header; /* Head of line list */ +extern LINE *tail; /* Last line in line list */ +extern LINE *top_line; /* First line of screen */ +extern LINE *bot_line; /* Last line of screen */ +extern LINE *cur_line; /* Current line in use */ +extern char *cur_text; /* Pointer to char on current line in use */ +extern int last_y; /* Last y of screen. Usually SCREENMAX */ +extern int ymax; +extern int screenmax; +extern char screen[SCREEN_SIZE];/* Output buffer for "writes" and "reads" */ + +extern int x, y; /* x, y coordinates on screen */ +extern FLAG modified; /* Set when file is modified */ +extern FLAG stat_visible; /* Set if status_line is visible */ +extern FLAG writable; /* Set if file cannot be written */ +extern FLAG quit; /* Set when quit character is typed */ +extern FLAG rpipe; /* Set if file should be read from stdin */ +extern int input_fd; /* Fd for command input */ +extern FLAG loading; /* Set if we're loading a file */ +extern int out_count; /* Index in output buffer */ +extern char file_name[LINE_LEN]; /* Name of file in use */ +extern char text_buffer[MAX_CHARS]; /* Buffer for modifying text */ +extern char *blank_line; /* Clear line to end */ + +extern char yank_file[]; /* Temp file for buffer */ +extern FLAG yank_status; /* Status of yank_file */ +extern long chars_saved; /* Nr of chars saved in buffer */ + +/* + * Empty output buffer + */ +#define clear_buffer() (out_count = 0) + +/* + * Print character on terminal + */ +#define putchar(c) (void) write_char(STD_OUT, (c)) + +/* + * Ring bell on terminal + */ +#define ring_bell() putchar('\07') + +/* + * Print string on terminal + */ +#define string_print(str) (void) writeline(STD_OUT, (str)) + +/* + * Flush output buffer + */ +#define flush() (void) flush_buffer(STD_OUT) + +/* + * Convert cnt to nearest tab position + */ +#define tab(cnt) (((cnt) + 8) & ~07) +#define is_tab(c) ((c) == '\t') + +/* + * Word defenitions + */ +#define white_space(c) ((c) == ' ' || (c) == '\t') +#define alpha(c) ((c) != ' ' && (c) != '\t' && (c) != '\n') + +/* + * Print line on terminal at offset 0 and clear tail of line + */ +#define line_print(line) put_line(line, 0, TRUE) + +/* + * Move to coordinates and set textp. (Don't use address) + */ +#define move_to(nx, ny) move((nx), NIL_PTR, (ny)) + +/* + * Move to coordinates on screen as indicated by textp. + */ +#define move_address(address) move(0, (address), y) + +/* + * Functions handling status_line. ON means in reverse video. + */ +#define status_line(str1, str2) (void) bottom_line(ON, (str1), \ + (str2), NIL_PTR, FALSE) +#define error(str1, str2) (void) bottom_line(ON, (str1), \ + (str2), NIL_PTR, FALSE) +#define get_string(str1,str2, fl) bottom_line(ON, (str1), NIL_PTR, (str2), fl) +#define clear_status() (void) bottom_line(OFF, NIL_PTR, NIL_PTR, \ + NIL_PTR, FALSE) + +/* + * Print info about current file and buffer. + */ +#define fstatus(mess, cnt) file_status((mess), (cnt), file_name, \ + nlines, writable, modified) + +/* + * Get real shift value. + */ +#define get_shift(cnt) ((cnt) & DUMMY_MASK) + +#endif /* YMAX */ + +/* mined1.c */ + +_PROTOTYPE(void FS, (void)); +_PROTOTYPE(void VI, (void)); +_PROTOTYPE(int WT, (void)); +_PROTOTYPE(void XWT, (void)); +_PROTOTYPE(void SH, (void)); +_PROTOTYPE(LINE *proceed, (LINE *line, int count )); +_PROTOTYPE(int bottom_line, (FLAG revfl, char *s1, char *s2, char *inbuf, FLAG statfl )); +_PROTOTYPE(int count_chars, (LINE *line )); +_PROTOTYPE(void move, (int new_x, char *new_address, int new_y )); +_PROTOTYPE(int find_x, (LINE *line, char *address )); +_PROTOTYPE(char *find_address, (LINE *line, int x_coord, int *old_x )); +_PROTOTYPE(int length_of, (char *string )); +_PROTOTYPE(void copy_string, (char *to, char *from )); +_PROTOTYPE(void reset, (LINE *head_line, int screen_y )); +_PROTOTYPE(void set_cursor, (int nx, int ny )); +_PROTOTYPE(void open_device, (void)); +_PROTOTYPE(int getchar, (void)); +_PROTOTYPE(void display, (int x_coord, int y_coord, LINE *line, int count )); +_PROTOTYPE(int write_char, (int fd, int c )); +_PROTOTYPE(int writeline, (int fd, char *text )); +_PROTOTYPE(void put_line, (LINE *line, int offset, FLAG clear_line )); +_PROTOTYPE(int flush_buffer, (int fd )); +_PROTOTYPE(void bad_write, (int fd )); +_PROTOTYPE(void catch, (int sig )); +_PROTOTYPE(void abort_mined, (void)); +_PROTOTYPE(void raw_mode, (FLAG state )); +_PROTOTYPE(void panic, (char *message )); +_PROTOTYPE(char *alloc, (int bytes )); +_PROTOTYPE(void free_space, (char *p )); +/* +#ifdef UNIX +_PROTOTYPE(void (*key_map [128]), (void)); +#else +_PROTOTYPE(void (*key_map [256]), (void)); +#endif +*/ +_PROTOTYPE(void initialize, (void)); +_PROTOTYPE(char *basename, (char *path )); +_PROTOTYPE(void load_file, (char *file )); +_PROTOTYPE(int get_line, (int fd, char *buffer )); +_PROTOTYPE(LINE *install_line, (char *buffer, int length )); +_PROTOTYPE(void main, (int argc, char *argv [])); +_PROTOTYPE(void RD, (void)); +_PROTOTYPE(void I, (void)); +_PROTOTYPE(void XT, (void)); +_PROTOTYPE(void ESC, (void)); +_PROTOTYPE(int ask_save, (void)); +_PROTOTYPE(int line_number, (void)); +_PROTOTYPE(void file_status, (char *message, long count, char *file, int lines, + FLAG writefl, FLAG changed )); +#if __STDC__ +void build_string(char *buf, char *fmt, ...); +#else +void build_string(); +#endif +_PROTOTYPE(char *num_out, (long number )); +_PROTOTYPE(int get_number, (char *message, int *result )); +_PROTOTYPE(int input, (char *inbuf, FLAG clearfl )); +_PROTOTYPE(int get_file, (char *message, char *file )); +_PROTOTYPE(int _getchar, (void)); +_PROTOTYPE(void _flush, (void)); +_PROTOTYPE(void _putchar, (int c )); +_PROTOTYPE(void get_term, (void)); + +/* mined2.c */ + +_PROTOTYPE(void UP, (void)); +_PROTOTYPE(void DN, (void)); +_PROTOTYPE(void LF, (void)); +_PROTOTYPE(void RT, (void)); +_PROTOTYPE(void HIGH, (void)); +_PROTOTYPE(void LOW, (void)); +_PROTOTYPE(void BL, (void)); +_PROTOTYPE(void EL, (void)); +_PROTOTYPE(void GOTO, (void)); +_PROTOTYPE(void PD, (void)); +_PROTOTYPE(void PU, (void)); +_PROTOTYPE(void HO, (void)); +_PROTOTYPE(void EF, (void)); +_PROTOTYPE(void SU, (void)); +_PROTOTYPE(void SD, (void)); +_PROTOTYPE(int forward_scroll, (void)); +_PROTOTYPE(int reverse_scroll, (void)); +_PROTOTYPE(void MP, (void)); +_PROTOTYPE(void move_previous_word, (FLAG remove )); +_PROTOTYPE(void MN, (void)); +_PROTOTYPE(void move_next_word, (FLAG remove )); +_PROTOTYPE(void DCC, (void)); +_PROTOTYPE(void DPC, (void)); +_PROTOTYPE(void DLN, (void)); +_PROTOTYPE(void DNW, (void)); +_PROTOTYPE(void DPW, (void)); +_PROTOTYPE(void S, (int character )); +_PROTOTYPE(void CTL, (void)); +_PROTOTYPE(void LIB, (void)); +_PROTOTYPE(LINE *line_insert, (LINE *line, char *string, int len )); +_PROTOTYPE(int insert, (LINE *line, char *location, char *string )); +_PROTOTYPE(LINE *line_delete, (LINE *line )); +_PROTOTYPE(void delete, (LINE *start_line, char *start_textp, LINE *end_line, char *end_textp )); +_PROTOTYPE(void PT, (void)); +_PROTOTYPE(void IF, (void)); +_PROTOTYPE(void file_insert, (int fd, FLAG old_pos )); +_PROTOTYPE(void WB, (void)); +_PROTOTYPE(void MA, (void)); +_PROTOTYPE(void YA, (void)); +_PROTOTYPE(void DT, (void)); +_PROTOTYPE(void set_up, (FLAG remove )); +_PROTOTYPE(FLAG checkmark, (void)); +_PROTOTYPE(int legal, (void)); +_PROTOTYPE(void yank, (LINE *start_line, char *start_textp, LINE *end_line, char *end_textp, FLAG remove )); +_PROTOTYPE(int scratch_file, (FLAG mode )); +_PROTOTYPE(void SF, (void)); +_PROTOTYPE(void SR, (void)); +_PROTOTYPE(REGEX *get_expression, (char *message )); +_PROTOTYPE(void GR, (void)); +_PROTOTYPE(void LR, (void)); +_PROTOTYPE(void change, (char *message, FLAG file )); +_PROTOTYPE(char *substitute, (LINE *line, REGEX *program, char *replacement )); +_PROTOTYPE(void search, (char *message, FLAG method )); +_PROTOTYPE(int find_y, (LINE *match_line )); +_PROTOTYPE(void finished, (REGEX *program, int *last_exp )); +_PROTOTYPE(void compile, (char *pattern, REGEX *program )); +_PROTOTYPE(LINE *match, (REGEX *program, char *string, FLAG method )); +_PROTOTYPE(int line_check, (REGEX *program, char *string, FLAG method )); +_PROTOTYPE(int check_string, (REGEX *program, char *string, int *expression )); +_PROTOTYPE(int star, (REGEX *program, char *end_position, char *string, int *expression )); +_PROTOTYPE(int in_list, (int *list, int c, int list_length, int opcode )); +_PROTOTYPE(void dummy_line, (void)); diff --git a/bin/mined/mined1.c b/bin/mined/mined1.c new file mode 100644 index 0000000000..bf2a76a093 --- /dev/null +++ b/bin/mined/mined1.c @@ -0,0 +1,1988 @@ +/* $DragonFly: src/bin/mined/mined1.c,v 1.1 2005/03/15 01:56:24 dillon Exp $ */ +/* + * Part one of the mined editor. + */ + +/* + * Author: Michiel Huisjes. + * + * 1. General remarks. + * + * Mined is a screen editor designed for the MINIX operating system. + * It is meant to be used on files not larger than 50K and to be fast. + * When mined starts up, it reads the file into its memory to minimize + * disk access. The only time that disk access is needed is when certain + * save, write or copy commands are given. + * + * Mined has the style of Emacs or Jove, that means that there are no modes. + * Each character has its own entry in an 256 pointer to function array, + * which is called when that character is typed. Only ASCII characters are + * connected with a function that inserts that character at the current + * location in the file. Two execptions are and which are + * inserted as well. Note that the mapping between commands and functions + * called is implicit in the table. Changing the mapping just implies + * changing the pointers in this table. + * + * The display consists of SCREENMAX + 1 lines and XMAX + 1 characters. When + * a line is larger (or gets larger during editing) than XBREAK characters, + * the line is either shifted SHIFT_SIZE characters to the left (which means + * that the first SHIFT_SIZE characters are not printed) or the end of the + * line is marked with the SHIFT_MARK character and the rest of the line is + * not printed. A line can never exceed MAX_CHARS characters. Mined will + * always try to keep the cursor on the same line and same (relative) + * x-coordinate if nothing changed. So if you scroll one line up, the cursor + * stays on the same line, or when you move one line down, the cursor will + * move to the same place on the line as it was on the previous. + * Every character on the line is available for editing including the + * linefeed at the the of the line. When the linefeed is deleted, the current + * line and the next line are joined. The last character of the file (which + * is always a linefeed) can never be deleted. + * The bottomline (as indicated by YMAX + 1) is used as a status line during + * editing. This line is usually blank or contains information mined needs + * during editing. This information (or rather questions) is displayed in + * reverse video. + * + * The terminal modes are changed completely. All signals like start/stop, + * interrupt etc. are unset. The only signal that remains is the quit signal. + * The quit signal (^\) is the general abort signal for mined. Typing a ^\ + * during searching or when mined is asking for filenames, etc. will abort + * the function and mined will return to the main loop. Sending a quit + * signal during the main loop will abort the session (after confirmation) + * and the file is not (!) saved. + * The session will also be aborted when an unrecoverable error occurs. E.g + * when there is no more memory available. If the file has been modified, + * mined will ask if the file has to be saved or not. + * If there is no more space left on the disk, mined will just give an error + * message and continue. + * + * The number of system calls are minized. This is done to keep the editor + * as fast as possible. I/O is done in SCREEN_SIZE reads/writes. Accumulated + * output is also flushed at the end of each character typed. + * + * 2. Regular expressions + * + * Mined has a build in regular expression matcher, which is used for + * searching and replace routines. A regular expression consists of a + * sequence of: + * + * 1. A normal character matching that character. + * 2. A . matching any character. + * 3. A ^ matching the begin of a line. + * 4. A $ (as last character of the pattern) mathing the end of a line. + * 5. A \ matching . + * 6. A number of characters enclosed in [] pairs matching any of these + * characters. A list of characters can be indicated by a '-'. So + * [a-z] matches any letter of the alphabet. If the first character + * after the '[' is a '^' then the set is negated (matching none of + * the characters). + * A ']', '^' or '-' can be escaped by putting a '\' in front of it. + * Of course this means that a \ must be represented by \\. + * 7. If one of the expressions as described in 1-6 is followed by a + * '*' than that expressions matches a sequence of 0 or more of + * that expression. + * + * Parsing of regular expression is done in two phases. In the first phase + * the expression is compiled into a more comprehensible form. In the second + * phase the actual matching is done. For more details see 3.6. + * + * + * 3. Implementation of mined. + * + * 3.1 Data structures. + * + * The main data structures are as follows. The whole file is kept in a + * double linked list of lines. The LINE structure looks like this: + * + * typedef struct Line { + * struct Line *next; + * struct Line *prev; + * char *text; + * unsigned char shift_count; + * } LINE; + * + * Each line entry contains a pointer to the next line, a pointer to the + * previous line and a pointer to the text of that line. A special field + * shift_count contains the number of shifts (in units of SHIFT_SIZE) + * that is performed on that line. The total size of the structure is 7 + * bytes so a file consisting of 1000 empty lines will waste a lot of + * memory. A LINE structure is allocated for each line in the file. After + * that the number of characters of the line is counted and sufficient + * space is allocated to store them (including a linefeed and a '\0'). + * The resulting address is assigned to the text field in the structure. + * + * A special structure is allocated and its address is assigned to the + * variable header as well as the variable tail. The text field of this + * structure is set to NIL_PTR. The tail->prev of this structure points + * to the last LINE of the file and the header->next to the first LINE. + * Other LINE *variables are top_line and bot_line which point to the + * first line resp. the last line on the screen. + * Two other variables are important as well. First the LINE *cur_line, + * which points to the LINE currently in use and the char *cur_text, + * which points to the character at which the cursor stands. + * Whenever an ASCII character is typed, a new line is build with this + * character inserted. Then the old data space (pointed to by + * cur_line->text) is freed, data space for the new line is allocated and + * assigned to cur_line->text. + * + * Two global variables called x and y represent the x and y coordinates + * from the cursor. The global variable nlines contains the number of + * lines in the file. Last_y indicates the maximum y coordinate of the + * screen (which is usually SCREENMAX). + * + * A few strings must be initialized by hand before compiling mined. + * These string are enter_string, which is printed upon entering mined, + * rev_video (turn on reverse video), normal_video, rev_scroll (perform a + * reverse scroll) and pos_string. The last string should hold the + * absolute position string to be printed for cursor motion. The #define + * X_PLUS and Y_PLUS should contain the characters to be added to the + * coordinates x and y (both starting at 0) to finish cursor positioning. + * + * 3.2 Starting up. + * + * Mined can be called with or without argument and the function + * load_file () is called with these arguments. load_file () checks + * if the file exists if it can be read and if it is writable and + * sets the writable flag accordingly. If the file can be read, + * load_file () reads a line from the file and stores this line into + * a structure by calling install_line () and line_insert () which + * installs the line into the double linked list, until the end of the + * file is reached. + * Lines are read by the function get_line (), which buffers the + * reading in blocks of SCREEN_SIZE. Load_file () also initializes the + * LINE *variables described above. + * + * 3.3 Moving around. + * + * Several commands are implemented for moving through the file. + * Moving up (UP), down (DN) left (LF) and right (RT) are done by the + * arrow keys. Moving one line below the screen scrolls the screen one + * line up. Moving one line above the screen scrolls the screen one line + * down. The functions forward_scroll () and reverse_scroll () take care + * of that. + * Several other move functions exist: move to begin of line (BL), end of + * line (EL) top of screen (HIGH), bottom of screen (LOW), top of file + * (HO), end of file (EF), scroll one page down (PD), scroll one page up + * (PU), scroll one line down (SD), scroll one line up (SU) and move to a + * certain line number (GOTO). + * Two functions called MN () and MP () each move one word further or + * backwards. A word is a number of non-blanks seperated by a space, a + * tab or a linefeed. + * + * 3.4 Modifying text. + * + * The modifying commands can be separated into two modes. The first + * being inserting text, and the other deleting text. Two functions are + * created for these purposes: insert () and delete (). Both are capable + * of deleting or inserting large amounts of text as well as one + * character. Insert () must be given the line and location at which + * the text must be inserted. Is doesn't make any difference whether this + * text contains linefeeds or not. Delete () must be given a pointer to + * the start line, a pointer from where deleting should start on that + * line and the same information about the end position. The last + * character of the file will never be deleted. Delete () will make the + * necessary changes to the screen after deleting, but insert () won't. + * The functions for modifying text are: insert one char (S), insert a + * file (file_insert (fd)), insert a linefeed and put cursor back to + * end of line (LIB), delete character under the cursor (DCC), delete + * before cursor (even linefeed) (DPC), delete next word (DNW), delete + * previous word (DPC) and delete to end of line (if the cursor is at + * a linefeed delete line) (DLN). + * + * 3.5 Yanking. + * + * A few utilities are provided for yanking pieces of text. The function + * MA () marks the current position in the file. This is done by setting + * LINE *mark_line and char *mark_text to the current position. Yanking + * of text can be done in two modes. The first mode just copies the text + * from the mark to the current position (or visa versa) into a buffer + * (YA) and the second also deletes the text (DT). Both functions call + * the function set_up () with the delete flag on or off. Set_up () + * checks if the marked position is still a valid one (by using + * check_mark () and legal ()), and then calls the function yank () with + * a start and end position in the file. This function copies the text + * into a scratch_file as indicated by the variable yank_file. This + * scratch_file is made uniq by the function scratch_file (). At the end + * of copying yank will (if necessary) delete the text. A global flag + * called yank_status keeps track of the buffer (or file) status. It is + * initialized on NOT_VALID and set to EMPTY (by set_up ()) or VALID (by + * yank ()). Several things can be done with the buffer. It can be + * inserted somewhere else in the file (PT) or it can be copied into + * another file (WB), which will be prompted for. + * + * 3.6 Search and replace routines. + * + * Searching for strings and replacing strings are done by regular + * expressions. For any expression the function compile () is called + * with as argument the expression to compile. Compile () returns a + * pointer to a structure which looks like this: + * + * typedef struct regex { + * union { + * char *err_mess; + * int *expression; + * } result; + * char status; + * char *start_ptr; + * char *end_ptr; + * } REGEX; + * + * If something went wrong during compiling (e.g. an illegal expression + * was given), the function reg_error () is called, which sets the status + * field to REG_ERROR and the err_mess field to the error message. If the + * match must be anchored at the beginning of the line (end of line), the + * status field is set to BEGIN_LINE (END_LINE). If none of these special + * cases are true, the field is set to 0 and the function finished () is + * called. Finished () allocates space to hold the compiled expression + * and copies this expression into the expression field of the union + * (bcopy ()). Matching is done by the routines match() and line_check(). + * Match () takes as argument the REGEX *program, a pointer to the + * startposition on the current line, and a flag indicating FORWARD or + * REVERSE search. Match () checks out the whole file until a match is + * found. If match is found it returns a pointer to the line in which the + * match was found else it returns a NIL_LINE. Line_check () takes the + * same arguments, but return either MATCH or NO_MATCH. + * During checking, the start_ptr and end_ptr fields of the REGEX + * structure are assigned to the start and end of the match. + * Both functions try to find a match by walking through the line + * character by character. For each possibility, the function + * check_string () is called with as arguments the REGEX *program and the + * string to search in. It starts walking through the expression until + * the end of the expression or the end of the string is reached. + * Whenever a * is encountered, this position of the string is marked, + * the maximum number of matches are performed and the function star () + * is called in order to try to find the longest match possible. Star () + * takes as arguments the REGEX program, the current position of the + * string, the marked position and the current position of the expression + * Star () walks from the current position of the string back to the + * marked position, and calls string_check () in order to find a match. + * It returns MATCH or NO_MATCH, just as string_check () does. + * Searching is now easy. Both search routines (forward (SF) and + * backwards search (SR)) call search () with an apropiate message and a + * flag indicating FORWARD or REVERSE search. Search () will get an + * expression from the user by calling get_expression(). Get_expression() + * returns a pointer to a REGEX structure or NIL_REG upon errors and + * prompts for the expression. If no expression if given, the previous is + * used instead. After that search will call match (), and if a match is + * found, we can move to that place in the file by the functions find_x() + * and find_y () which will find display the match on the screen. + * Replacing can be done in two ways. A global replace (GR) or a line + * replace (LR). Both functions call change () with a message an a flag + * indicating global or line replacement. Change () will prompt for the + * expression and for the replacement. Every & in the replacement pattern + * means substitute the match instead. An & can be escaped by a \. When + * a match is found, the function substitute () will perform the + * substitution. + * + * 3.6 Miscellaneous commands. + * + * A few commands haven't be discussed yet. These are redraw the screen + * (RD) fork a shell (SH), print file status (FS), write file to disc + * (WT), insert a file at current position (IF), leave editor (XT) and + * visit another file (VI). The last two functions will check if the file + * has been modified. If it has, they will ask if you want to save the + * file by calling ask_save (). + * The function ESC () will repeat a command n times. It will prompt for + * the number. Aborting the loop can be done by sending the ^\ signal. + * + * 3.7 Utility functions. + * + * Several functions exists for internal use. First allocation routines: + * alloc (bytes) and newline () will return a pointer to free data space + * if the given size. If there is no more memory available, the function + * panic () is called. + * Signal handling: The only signal that can be send to mined is the + * SIGQUIT signal. This signal, functions as a general abort command. + * Mined will abort if the signal is given during the main loop. The + * function abort_mined () takes care of that. + * Panic () is a function with as argument a error message. It will print + * the message and the error number set by the kernel (errno) and will + * ask if the file must be saved or not. It resets the terminal + * (raw_mode ()) and exits. + * String handling routines like copy_string(to, from), length_of(string) + * and build_string (buffer, format, arg1, arg2, ...). The latter takes + * a description of the string out out the format field and puts the + * result in the buffer. (It works like printf (3), but then into a + * string). The functions status_line (string1, string2), error (string1, + * string2), clear_status () and bottom_line () all print information on + * the status line. + * Get_string (message, buffer) reads a string and getchar () reads one + * character from the terminal. + * Num_out ((long) number) prints the number into a 11 digit field + * without leading zero's. It returns a pointer to the resulting string. + * File_status () prints all file information on the status line. + * Set_cursor (x, y) prints the string to put the cursor at coordinates + * x and y. + * Output is done by four functions: writeline(fd,string), clear_buffer() + * write_char (fd, c) and flush_buffer (fd). Three defines are provided + * to write on filedescriptor STD_OUT (terminal) which is used normally: + * string_print (string), putchar (c) and flush (). All these functions + * use the global I/O buffer screen and the global index for this array + * called out_count. In this way I/O can be buffered, so that reads or + * writes can be done in blocks of SCREEN_SIZE size. + * The following functions all handle internal line maintenance. The + * function proceed (start_line, count) returns the count'th line after + * start_line. If count is negative, the count'th line before the + * start_line is returned. If header or tail is encountered then that + * will be returned. Display (x, y, start_line, count) displays count + * lines starting at coordinates [x, y] and beginning at start_line. If + * the header or tail is encountered, empty lines are displayed instead. + * The function reset (head_line, ny) reset top_line, last_y, bot_line, + * cur_line and y-coordinate. This is not a neat way to do the + * maintenance, but it sure saves a lot of code. It is usually used in + * combination with display (). + * Put_line(line, offset, clear_line), prints a line (skipping characters + * according to the line->shift_size field) until XBREAK - offset + * characters are printed or a '\n' is encountered. If clear_line is + * TRUE, spaces are printed until XBREAK - offset characters. + * Line_print (line) is a #define from put_line (line, 0, TRUE). + * Moving is done by the functions move_to (x, y), move_addres (address) + * and move (x, adress, y). This function is the most important one in + * mined. New_y must be between 0 and last_y, new_x can be about + * anything, address must be a pointer to an character on the current + * line (or y). Move_to () first adjust the y coordinate together with + * cur_line. If an address is given, it finds the corresponding + * x-coordinate. If an new x-coordinate was given, it will try to locate + * the corresponding character. After that it sets the shift_count field + * of cur_line to an apropiate number according to new_x. The only thing + * left to do now is to assign the new values to cur_line, cur_text, x + * and y. + * + * 4. Summary of commands. + * + * CURSOR MOTION + * up-arrow Move cursor 1 line up. At top of screen, reverse scroll + * down-arrow Move cursor 1 line down. At bottom, scroll forward. + * left-arrow Move cursor 1 character left or to end of previous line + * right-arrow Move cursor 1 character right or to start of next line + * CTRL-A Move cursor to start of current line + * CTRL-Z Move cursor to end of current line + * CTRL-^ Move cursor to top of screen + * CTRL-_ Move cursor to bottom of screen + * CTRL-F Forward to start of next word (even to next line) + * CTRL-B Backward to first character of previous word + * + * SCREEN MOTION + * Home key Move cursor to first character of file + * End key Move cursor to last character of file + * PgUp Scroll backward 1 page. Bottom line becomes top line + * PgD Scroll backward 1 page. Top line becomes bottom line + * CTRL-D Scroll screen down one line (reverse scroll) + * CTRL-U Scroll screen up one line (forward scroll) + * + * MODIFYING TEXT + * ASCII char Self insert character at cursor + * tab Insert tab at cursor + * backspace Delete the previous char (left of cursor), even line feed + * Del Delete the character under the cursor + * CTRL-N Delete next word + * CTRL-P Delete previous word + * CTRL-O Insert line feed at cursor and back up 1 character + * CTRL-T Delete tail of line (cursor to end); if empty, delete line + * CTRL-@ Set the mark (remember the current location) + * CTRL-K Delete text from the mark to current position save on file + * CTRL-C Save the text from the mark to the current position + * CTRL-Y Insert the contents of the save file at current position + * CTRL-Q Insert the contents of the save file into a new file + * CTRL-G Insert a file at the current position + * + * MISCELLANEOUS + * CTRL-E Erase and redraw the screen + * CTRL-V Visit file (read a new file); complain if old one changed + * CTRL-W Write the current file back to the disk + * numeric + Search forward (prompt for regular expression) + * numeric - Search backward (prompt for regular expression) + * numeric 5 Print the current status of the file + * CTRL-R (Global) Replace str1 by str2 (prompts for each string) + * CTRL-L (Line) Replace string1 by string2 + * CTRL-S Fork off a shell and wait for it to finish + * CTRL-X EXIT (prompt if file modified) + * CTRL-] Go to a line. Prompts for linenumber + * CTRL-\ Abort whatever editor was doing and start again + * escape key Repeat a command count times; (prompts for count) + */ + +/* ======================================================================== * + * Utilities * + * ======================================================================== */ + +#include "mined.h" +#include +#include +#include +#include +#include +#include +#if __STDC__ +#include +#else +#include +#endif + +extern int errno; +int ymax = YMAX; +int screenmax = SCREENMAX; + + +/* + * Print file status. + */ +void FS() +{ + fstatus(file_name[0] ? "" : "[buffer]", -1L); +} + +/* + * Visit (edit) another file. If the file has been modified, ask the user if + * he wants to save it. + */ +void VI() +{ + char new_file[LINE_LEN]; /* Buffer to hold new file name */ + + if (modified == TRUE && ask_save() == ERRORS) + return; + +/* Get new file name */ + if (get_file("Visit file:", new_file) == ERRORS) + return; + +/* Free old linked list, initialize global variables and load new file */ + initialize(); +#ifdef UNIX + tputs(CL, 0, _putchar); +#else + string_print (enter_string); +#endif /* UNIX */ + load_file(new_file[0] == '\0' ? NIL_PTR : new_file); +} + +/* + * Write file in core to disc. + */ +int WT() +{ + register LINE *line; + register long count = 0L; /* Nr of chars written */ + char file[LINE_LEN]; /* Buffer for new file name */ + int fd; /* Filedescriptor of file */ + + if (modified == FALSE) { + error ("Write not necessary.", NIL_PTR); + return FINE; + } + +/* Check if file_name is valid and if file can be written */ + if (file_name[0] == '\0' || writable == FALSE) { + if (get_file("Enter file name:", file) != FINE) + return ERRORS; + copy_string(file_name, file); /* Save file name */ + } + if ((fd = creat(file_name, 0644)) < 0) { /* Empty file */ + error("Cannot create ", file_name); + writable = FALSE; + return ERRORS; + } + else + writable = TRUE; + + clear_buffer(); + + status_line("Writing ", file_name); + for (line = header->next; line != tail; line = line->next) { + if (line->shift_count & DUMMY) { + if (line->next == tail && line->text[0] == '\n') + continue; + } + if (writeline(fd, line->text) == ERRORS) { + count = -1L; + break; + } + count += (long) length_of(line->text); + } + + if (count > 0L && flush_buffer(fd) == ERRORS) + count = -1L; + + (void) close(fd); + + if (count == -1L) + return ERRORS; + + modified = FALSE; + rpipe = FALSE; /* File name is now assigned */ + +/* Display how many chars (and lines) were written */ + fstatus("Wrote", count); + return FINE; +} + +/* Call WT and discard value returned. */ +void XWT() +{ + (void) WT(); +} + + + +/* + * Call an interactive shell. + */ +void SH() +{ + register int w; + int pid, status; + char *shell; + + if ((shell = getenv("SHELL")) == NIL_PTR) shell = "/bin/sh"; + + switch (pid = fork()) { + case -1: /* Error */ + error("Cannot fork.", NIL_PTR); + return; + case 0: /* This is the child */ + set_cursor(0, ymax); + putchar('\n'); + flush(); + raw_mode(OFF); + if (rpipe) { /* Fix stdin */ + close (0); + if (open("/dev/tty", 0) < 0) + exit (126); + } + execl(shell, shell, (char *) 0); + exit(127); /* Exit with 127 */ + default : /* This is the parent */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + do { + w = wait(&status); + } while (w != -1 && w != pid); + } + + raw_mode(ON); + RD(); + + if ((status >> 8) == 127) /* Child died with 127 */ + error("Cannot exec ", shell); + else if ((status >> 8) == 126) + error("Cannot open /dev/tty as fd #0", NIL_PTR); +} + +/* + * Proceed returns the count'th line after `line'. When count is negative + * it returns the count'th line before `line'. When the next (previous) + * line is the tail (header) indicating EOF (tof) it stops. + */ +LINE *proceed(line, count) +register LINE *line; +register int count; +{ + if (count < 0) + while (count++ < 0 && line != header) + line = line->prev; + else + while (count-- > 0 && line != tail) + line = line->next; + return line; +} + +/* + * Show concatenation of s1 and s2 on the status line (bottom of screen) + * If revfl is TRUE, turn on reverse video on both strings. Set stat_visible + * only if bottom_line is visible. + */ +int bottom_line(revfl, s1, s2, inbuf, statfl) +FLAG revfl; +char *s1, *s2; +char *inbuf; +FLAG statfl; +{ + int ret = FINE; + char buf[LINE_LEN]; + register char *p = buf; + + *p++ = ' '; + if (s1 != NIL_PTR) + while (*p = *s1++) + p++; + if (s2 != NIL_PTR) + while (*p = *s2++) + p++; + *p++ = ' '; + *p++ = 0; + + if (revfl == ON && stat_visible == TRUE) + clear_status (); + set_cursor(0, ymax); + if (revfl == ON) { /* Print rev. start sequence */ +#ifdef UNIX + tputs(SO, 0, _putchar); +#else + string_print(rev_video); +#endif /* UNIX */ + stat_visible = TRUE; + } + else /* Used as clear_status() */ + stat_visible = FALSE; + + string_print(buf); + + if (inbuf != NIL_PTR) + ret = input(inbuf, statfl); + + /* Print normal video */ +#ifdef UNIX + tputs(SE, 0, _putchar); + tputs(CE, 0, _putchar); +#else + string_print(normal_video); + string_print(blank_line); /* Clear the rest of the line */ +#endif /* UNIX */ + if (inbuf != NIL_PTR) + set_cursor(0, ymax); + else + set_cursor(x, y); /* Set cursor back to old position */ + flush(); /* Perform the actual write */ + if (ret != FINE) + clear_status(); + return ret; +} + +/* + * Count_chars() count the number of chars that the line would occupy on the + * screen. Counting starts at the real x-coordinate of the line. + */ +int count_chars(line) +LINE *line; +{ + register int cnt = get_shift(line->shift_count) * -SHIFT_SIZE; + register char *textp = line->text; + +/* Find begin of line on screen */ + while (cnt < 0) { + if (is_tab(*textp++)) + cnt = tab(cnt); + else + cnt++; + } + +/* Count number of chars left */ + cnt = 0; + while (*textp != '\n') { + if (is_tab(*textp++)) + cnt = tab(cnt); + else + cnt++; + } + return cnt; +} + +/* + * Move to coordinates nx, ny at screen. The caller must check that scrolling + * is not needed. + * If new_x is lower than 0 or higher than XBREAK, move_to() will check if + * the line can be shifted. If it can it sets(or resets) the shift_count field + * of the current line accordingly. + * Move also sets cur_text to the right char. + * If we're moving to the same x coordinate, try to move the the x-coordinate + * used on the other previous call. + */ +void move(new_x, new_address, new_y) +register int new_x; +int new_y; +char *new_address; +{ + register LINE *line = cur_line; /* For building new cur_line */ + int shift = 0; /* How many shifts to make */ + static int rel_x = 0; /* Remember relative x position */ + int tx = x; + +/* Check for illegal values */ + if (new_y < 0 || new_y > last_y) + return; + +/* Adjust y-coordinate and cur_line */ + if (new_y < y) + while (y != new_y) { + y--; + line = line->prev; + } + else + while (y != new_y) { + y++; + line = line->next; + } + +/* Set or unset relative x-coordinate */ + if (new_address == NIL_PTR) { + new_address = find_address(line, (new_x == x) ? rel_x : new_x , &tx); + if (new_x != x) + rel_x = tx; + new_x = tx; + } + else + rel_x = new_x = find_x(line, new_address); + +/* Adjust shift_count if new_x lower than 0 or higher than XBREAK */ + if (new_x < 0 || new_x >= XBREAK) { + if (new_x > XBREAK || (new_x == XBREAK && *new_address != '\n')) + shift = (new_x - XBREAK) / SHIFT_SIZE + 1; + else { + shift = new_x / SHIFT_SIZE; + if (new_x % SHIFT_SIZE) + shift--; + } + + if (shift != 0) { + line->shift_count += shift; + new_x = find_x(line, new_address); + set_cursor(0, y); + line_print(line); + rel_x = new_x; + } + } + +/* Assign and position cursor */ + x = new_x; + cur_text = new_address; + cur_line = line; + set_cursor(x, y); +} + +/* + * Find_x() returns the x coordinate belonging to address. + * (Tabs are expanded). + */ +int find_x(line, address) +LINE *line; +char *address; +{ + register char *textp = line->text; + register int nx = get_shift(line->shift_count) * -SHIFT_SIZE; + + while (textp != address && *textp != '\0') { + if (is_tab(*textp++)) /* Expand tabs */ + nx = tab(nx); + else + nx++; + } + return nx; +} + +/* + * Find_address() returns the pointer in the line with offset x_coord. + * (Tabs are expanded). + */ +char *find_address(line, x_coord, old_x) +LINE *line; +int x_coord; +int *old_x; +{ + register char *textp = line->text; + register int tx = get_shift(line->shift_count) * -SHIFT_SIZE; + + while (tx < x_coord && *textp != '\n') { + if (is_tab(*textp)) { + if (*old_x - x_coord == 1 && tab(tx) > x_coord) + break; /* Moving left over tab */ + else + tx = tab(tx); + } + else + tx++; + textp++; + } + + *old_x = tx; + return textp; +} + +/* + * Length_of() returns the number of characters int the string `string' + * excluding the '\0'. + */ +int length_of(string) +register char *string; +{ + register int count = 0; + + if (string != NIL_PTR) { + while (*string++ != '\0') + count++; + } + return count; +} + +/* + * Copy_string() copies the string `from' into the string `to'. `To' must be + * long enough to hold `from'. + */ +void copy_string(to, from) +register char *to; +register char *from; +{ + while (*to++ = *from++) + ; +} + +/* + * Reset assigns bot_line, top_line and cur_line according to `head_line' + * which must be the first line of the screen, and an y-coordinate, + * which will be the current y-coordinate (if it isn't larger than last_y) + */ +void reset(head_line, screen_y) +LINE *head_line; +int screen_y; +{ + register LINE *line; + + top_line = line = head_line; + +/* Search for bot_line (might be last line in file) */ + for (last_y = 0; last_y < nlines - 1 && last_y < screenmax + && line->next != tail; last_y++) + line = line->next; + + bot_line = line; + y = (screen_y > last_y) ? last_y : screen_y; + +/* Set cur_line according to the new y value */ + cur_line = proceed(top_line, y); +} + +/* + * Set cursor at coordinates x, y. + */ +void set_cursor(nx, ny) +int nx, ny; +{ +#ifdef UNIX + extern char *tgoto(); + + tputs(tgoto(CM, nx, ny), 0, _putchar); +#else + char text_buffer[10]; + + build_string(text_buffer, pos_string, ny+1, nx+1); + string_print(text_buffer); +#endif /* UNIX */ +} + +/* + * Routine to open terminal when mined is used in a pipeline. + */ +void open_device() +{ + if ((input_fd = open("/dev/tty", 0)) < 0) + panic("Cannot open /dev/tty for read"); +} + +/* + * Getchar() reads one character from the terminal. The character must be + * masked with 0377 to avoid sign extension. + */ +int getchar() +{ +#ifdef UNIX + return (_getchar() & 0377); +#else + char c; + + if (read(input_fd, &c, 1) != 1 && quit == FALSE) + panic("Can't read one char from fd #0"); + + return c & 0377; +#endif /* UNIX */ +} + +/* + * Display() shows count lines on the terminal starting at the given + * coordinates. When the tail of the list is encountered it will fill the + * rest of the screen with blank_line's. + * When count is negative, a backwards print from `line' will be done. + */ +void display(x_coord, y_coord, line, count) +int x_coord, y_coord; +register LINE *line; +register int count; +{ + set_cursor(x_coord, y_coord); + +/* Find new startline if count is negative */ + if (count < 0) { + line = proceed(line, count); + count = -count; + } + +/* Print the lines */ + while (line != tail && count-- >= 0) { + line_print(line); + line = line->next; + } + +/* Print the blank lines (if any) */ + if (loading == FALSE) { + while (count-- >= 0) { +#ifdef UNIX + tputs(CE, 0, _putchar); +#else + string_print(blank_line); +#endif /* UNIX */ + putchar('\n'); + } + } +} + +/* + * Write_char does a buffered output. + */ +int write_char(fd, c) +int fd; +char c; +{ + screen [out_count++] = c; + if (out_count == SCREEN_SIZE) /* Flush on SCREEN_SIZE chars */ + return flush_buffer(fd); + return FINE; +} + +/* + * Writeline writes the given string on the given filedescriptor. + */ +int writeline(fd, text) +register int fd; +register char *text; +{ + while(*text) + if (write_char(fd, *text++) == ERRORS) + return ERRORS; + return FINE; +} + +/* + * Put_line print the given line on the standard output. If offset is not zero + * printing will start at that x-coordinate. If the FLAG clear_line is TRUE, + * then (screen) line will be cleared when the end of the line has been + * reached. + */ +void put_line(line, offset, clear_line) +LINE *line; /* Line to print */ +int offset; /* Offset to start */ +FLAG clear_line; /* Clear to eoln if TRUE */ +{ + register char *textp = line->text; + register int count = get_shift(line->shift_count) * -SHIFT_SIZE; + int tab_count; /* Used in tab expansion */ + +/* Skip all chars as indicated by the offset and the shift_count field */ + while (count < offset) { + if (is_tab(*textp++)) + count = tab(count); + else + count++; + } + + while (*textp != '\n' && count < XBREAK) { + if (is_tab(*textp)) { /* Expand tabs to spaces */ + tab_count = tab(count); + while (count < XBREAK && count < tab_count) { + count++; + putchar(' '); + } + textp++; + } + else { + if (*textp >= '\01' && *textp <= '\037') { +#ifdef UNIX + tputs(SO, 0, _putchar); +#else + string_print (rev_video); +#endif /* UNIX */ + putchar(*textp++ + '\100'); +#ifdef UNIX + tputs(SE, 0, _putchar); +#else + string_print (normal_video); +#endif /* UNIX */ + } + else + putchar(*textp++); + count++; + } + } + +/* If line is longer than XBREAK chars, print the shift_mark */ + if (count == XBREAK && *textp != '\n') + putchar(textp[1]=='\n' ? *textp : SHIFT_MARK); + +/* Clear the rest of the line is clear_line is TRUE */ + if (clear_line == TRUE) { +#ifdef UNIX + tputs(CE, 0, _putchar); +#else + string_print(blank_line); +#endif /* UNIX */ + putchar('\n'); + } +} + +/* + * Flush the I/O buffer on filedescriptor fd. + */ +int flush_buffer(fd) +int fd; +{ + if (out_count <= 0) /* There is nothing to flush */ + return FINE; +#ifdef UNIX + if (fd == STD_OUT) { + printf("%.*s", out_count, screen); + _flush(); + } + else +#endif /* UNIX */ + if (write(fd, screen, out_count) != out_count) { + bad_write(fd); + return ERRORS; + } + clear_buffer(); /* Empty buffer */ + return FINE; +} + +/* + * Bad_write() is called when a write failed. Notify the user. + */ +void bad_write(fd) +int fd; +{ + if (fd == STD_OUT) /* Cannot write to terminal? */ + exit(1); + + clear_buffer(); + build_string(text_buffer, "Command aborted: %s (File incomplete)", + (errno == ENOSPC || errno == -ENOSPC) ? + "No space on device" : "Write error"); + error(text_buffer, NIL_PTR); +} + +/* + * Catch the SIGQUIT signal (^\) send to mined. It turns on the quitflag. + */ +void catch(sig) +int sig; +{ +/* Reset the signal */ + signal(SIGQUIT, catch); + quit = TRUE; +} + +/* + * Abort_mined() will leave mined. Confirmation is asked first. + */ +void abort_mined() +{ + quit = FALSE; + +/* Ask for confirmation */ + status_line("Really abort? ", NIL_PTR); + if (getchar() != 'y') { + clear_status(); + return; + } + +/* Reset terminal */ + raw_mode(OFF); + set_cursor(0, ymax); + putchar('\n'); + flush(); +#ifdef UNIX + abort(); +#else + exit(1); +#endif /* UNIX */ +} + +#define UNDEF _POSIX_VDISABLE + +/* + * Set and reset tty into CBREAK or old mode according to argument `state'. It + * also sets all signal characters (except for ^\) to UNDEF. ^\ is caught. + */ +void raw_mode(state) +FLAG state; +{ + static struct termios old_tty; + static struct termios new_tty; + + if (state == OFF) { + tcsetattr(input_fd, TCSANOW, &old_tty); + return; + } + +/* Save old tty settings */ + tcgetattr(input_fd, &old_tty); + +/* Set tty to CBREAK mode */ + tcgetattr(input_fd, &new_tty); + new_tty.c_lflag &= ~(ICANON|ECHO|ECHONL); + new_tty.c_iflag &= ~(IXON|IXOFF); + +/* Unset signal chars, leave only SIGQUIT set to ^\ */ + new_tty.c_cc[VINTR] = new_tty.c_cc[VSUSP] = UNDEF; + new_tty.c_cc[VQUIT] = '\\' & 037; + signal(SIGQUIT, catch); /* Which is caught */ + + tcsetattr(input_fd, TCSANOW, &new_tty); +} + +/* + * Panic() is called with an error number and a message. It is called when + * something unrecoverable has happened. + * It writes the message to the terminal, resets the tty and exits. + * Ask the user if he wants to save his file. + */ +void panic(message) +register char *message; +{ + extern char yank_file[]; + +#ifdef UNIX + tputs(CL, 0, _putchar); + build_string(text_buffer, "%s\nError code %d\n", message, errno); +#else + build_string(text_buffer, "%s%s\nError code %d\n", enter_string, message, errno); +#endif /* UNIX */ + (void) write(STD_OUT, text_buffer, length_of(text_buffer)); + + if (loading == FALSE) + XT(); /* Check if file can be saved */ + else + (void) unlink(yank_file); + raw_mode(OFF); + +#ifdef UNIX + abort(); +#else + exit(1); +#endif /* UNIX */ +} + +char *alloc(bytes) +int bytes; +{ + char *p; + + p = malloc((unsigned) bytes); + if (p == NIL_PTR) { + if (loading == TRUE) + panic("File too big."); + panic("Out of memory."); + } + return(p); +} + +void free_space(p) +char *p; +{ + free(p); +} + +/* ======================================================================== * + * Main loops * + * ======================================================================== */ + +/* The mapping between input codes and functions. */ + +void (*key_map[256])() = { /* map ASCII characters to functions */ + /* 000-017 */ MA, BL, MP, YA, SD, RD, MN, IF, DPC, S, S, DT, LR, S, DNW,LIB, + /* 020-037 */ DPW, WB, GR, SH, DLN, SU, VI, XWT, XT, PT, EL, ESC, I, GOTO, + HIGH, LOW, + /* 040-057 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, + /* 060-077 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, + /* 100-117 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, + /* 120-137 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, + /* 140-157 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, + /* 160-177 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, DCC, + /* 200-217 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, + /* 220-237 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, + /* 240-257 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, + /* 260-277 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, + /* 300-317 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, + /* 320-337 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, + /* 340-357 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, + /* 360-377 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, +}; + +int nlines; /* Number of lines in file */ +LINE *header; /* Head of line list */ +LINE *tail; /* Last line in line list */ +LINE *cur_line; /* Current line in use */ +LINE *top_line; /* First line of screen */ +LINE *bot_line; /* Last line of screen */ +char *cur_text; /* Current char on current line in use */ +int last_y; /* Last y of screen. Usually SCREENMAX */ +char screen[SCREEN_SIZE]; /* Output buffer for "writes" and "reads" */ + +int x, y; /* x, y coordinates on screen */ +FLAG modified = FALSE; /* Set when file is modified */ +FLAG stat_visible; /* Set if status_line is visible */ +FLAG writable; /* Set if file cannot be written */ +FLAG loading; /* Set if we are loading a file. */ +FLAG quit = FALSE; /* Set when quit character is typed */ +FLAG rpipe = FALSE; /* Set if file should be read from stdin */ +int input_fd = 0; /* Fd for command input */ +int out_count; /* Index in output buffer */ +char file_name[LINE_LEN]; /* Name of file in use */ +char text_buffer[MAX_CHARS]; /* Buffer for modifying text */ + +/* Escape sequences. */ +#ifdef UNIX +char *CE, *VS, *SO, *SE, *CL, *AL, *CM; +#else +char *enter_string = "\033[H\033[J"; /* String printed on entering mined */ +char *pos_string = "\033[%d;%dH"; /* Absolute cursor position */ +char *rev_scroll = "\033M"; /* String for reverse scrolling */ +char *rev_video = "\033[7m"; /* String for starting reverse video */ +char *normal_video = "\033[m"; /* String for leaving reverse video */ +char *blank_line = "\033[K"; /* Clear line to end */ +#endif /* UNIX */ + +/* + * Yank variables. + */ +FLAG yank_status = NOT_VALID; /* Status of yank_file */ +char yank_file[] = "/tmp/mined.XXXXXX"; +long chars_saved; /* Nr of chars in buffer */ + +/* + * Initialize is called when a another file is edited. It free's the allocated + * space and sets modified back to FALSE and fixes the header/tail pointer. + */ +void initialize() +{ + register LINE *line, *next_line; + +/* Delete the whole list */ + for (line = header->next; line != tail; line = next_line) { + next_line = line->next; + free_space(line->text); + free_space((char*)line); + } + +/* header and tail should point to itself */ + line->next = line->prev = line; + x = y = 0; + rpipe = modified = FALSE; +} + +/* + * Basename() finds the absolute name of the file out of a given path_name. + */ +char *basename(path) +char *path; +{ + register char *ptr = path; + register char *last = NIL_PTR; + + while (*ptr != '\0') { + if (*ptr == '/') + last = ptr; + ptr++; + } + if (last == NIL_PTR) + return path; + if (*(last + 1) == '\0') { /* E.g. /usr/tmp/pipo/ */ + *last = '\0'; + return basename(path);/* Try again */ + } + return last + 1; +} + +/* + * Load_file loads the file `file' into core. If file is a NIL_PTR or the file + * couldn't be opened, just some initializations are done, and a line consisting + * of a `\n' is installed. + */ +void load_file(file) +char *file; +{ + register LINE *line = header; + register int len; + long nr_of_chars = 0L; + int fd = -1; /* Filedescriptor for file */ + + nlines = 0; /* Zero lines to start with */ + +/* Open file */ + writable = TRUE; /* Benefit of the doubt */ + if (file == NIL_PTR) { + if (rpipe == FALSE) + status_line("No file.", NIL_PTR); + else { + fd = 0; + file = "standard input"; + } + file_name[0] = '\0'; + } + else { + copy_string(file_name, file); /* Save file name */ + if (access(file, 0) < 0) /* Cannot access file. */ + status_line("New file ", file); + else if ((fd = open(file, 0)) < 0) + status_line("Cannot open ", file); + else if (access(file, 2) != 0) /* Set write flag */ + writable = FALSE; + } + +/* Read file */ + loading = TRUE; /* Loading file, so set flag */ + + if (fd >= 0) { + status_line("Reading ", file); + while ((len = get_line(fd, text_buffer)) != ERRORS) { + line = line_insert(line, text_buffer, len); + nr_of_chars += (long) len; + } + if (nlines == 0) /* The file was empty! */ + line = line_insert(line, "\n", 1); + clear_buffer(); /* Clear output buffer */ + cur_line = header->next; + fstatus("Read", nr_of_chars); + (void) close(fd); /* Close file */ + } + else /* Just install a "\n" */ + (void) line_insert(line, "\n", 1); + + reset(header->next, 0); /* Initialize pointers */ + +/* Print screen */ + display (0, 0, header->next, last_y); + move_to (0, 0); + flush(); /* Flush buffer */ + loading = FALSE; /* Stop loading, reset flag */ +} + + +/* + * Get_line reads one line from filedescriptor fd. If EOF is reached on fd, + * get_line() returns ERRORS, else it returns the length of the string. + */ +int get_line(fd, buffer) +int fd; +register char *buffer; +{ + static char *last = NIL_PTR; + static char *current = NIL_PTR; + static int read_chars; + register char *cur_pos = current; + char *begin = buffer; + + do { + if (cur_pos == last) { + if ((read_chars = read(fd, screen, SCREEN_SIZE)) <= 0) + break; + last = &screen[read_chars]; + cur_pos = screen; + } + if (*cur_pos == '\0') + *cur_pos = ' '; + } while ((*buffer++ = *cur_pos++) != '\n'); + + current = cur_pos; + if (read_chars <= 0) { + if (buffer == begin) + return ERRORS; + if (*(buffer - 1) != '\n') + if (loading == TRUE) /* Add '\n' to last line of file */ + *buffer++ = '\n'; + else { + *buffer = '\0'; + return NO_LINE; + } + } + + *buffer = '\0'; + return buffer - begin; +} + +/* + * Install_line installs the buffer into a LINE structure It returns a pointer + * to the allocated structure. + */ +LINE *install_line(buffer, length) +char *buffer; +int length; +{ + register LINE *new_line = (LINE *) alloc(sizeof(LINE)); + + new_line->text = alloc(length + 1); + new_line->shift_count = 0; + copy_string(new_line->text, buffer); + + return new_line; +} + +void main(argc, argv) +int argc; +char *argv[]; +{ +/* mined is the Minix editor. */ + + register int index; /* Index in key table */ + struct winsize winsize; + +#ifdef UNIX + get_term(); + tputs(VS, 0, _putchar); + tputs(CL, 0, _putchar); +#else + string_print(enter_string); /* Hello world */ +#endif /* UNIX */ + if (ioctl(STD_OUT, TIOCGWINSZ, &winsize) == 0 && winsize.ws_row != 0) { + ymax = winsize.ws_row - 1; + screenmax = ymax - 1; + } + + if (!isatty(0)) { /* Reading from pipe */ + if (argc != 1) { + write(2, "Cannot find terminal.\n", 22); + exit (1); + } + rpipe = TRUE; + modified = TRUE; /* Set modified so he can write */ + open_device(); + } + + raw_mode(ON); /* Set tty to appropriate mode */ + + header = tail = (LINE *) alloc(sizeof(LINE)); /* Make header of list*/ + header->text = NIL_PTR; + header->next = tail->prev = header; + +/* Load the file (if any) */ + if (argc < 2) + load_file(NIL_PTR); + else { + (void) get_file(NIL_PTR, argv[1]); /* Truncate filename */ + load_file(argv[1]); + } + + /* Main loop of the editor. */ + for (;;) { + index = getchar(); + if (stat_visible == TRUE) + clear_status(); + if (quit == TRUE) + abort_mined(); + else { /* Call the function for this key */ + (*key_map[index])(index); + flush(); /* Flush output (if any) */ + if (quit == TRUE) + quit = FALSE; + } + } + /* NOTREACHED */ +} + +/* ======================================================================== * + * Miscellaneous * + * ======================================================================== */ + +/* + * Redraw the screen + */ +void RD() +{ +/* Clear screen */ +#ifdef UNIX + tputs(VS, 0, _putchar); + tputs(CL, 0, _putchar); +#else + string_print(enter_string); +#endif /* UNIX */ + +/* Print first page */ + display(0, 0, top_line, last_y); + +/* Clear last line */ + set_cursor(0, ymax); +#ifdef UNIX + tputs(CE, 0, _putchar); +#else + string_print(blank_line); +#endif /* UNIX */ + move_to(x, y); +} + +/* + * Ignore this keystroke. + */ +void I() +{ +} + +/* + * Leave editor. If the file has changed, ask if the user wants to save it. + */ +void XT() +{ + if (modified == TRUE && ask_save() == ERRORS) + return; + + raw_mode(OFF); + set_cursor(0, ymax); + putchar('\n'); + flush(); + (void) unlink(yank_file); /* Might not be necessary */ + exit(0); +} + +void (*escfunc(c))() +int c; +{ +#if (CHIP == M68000) +#ifndef COMPAT + int ch; +#endif +#endif + if (c == '[') { + /* Start of ASCII escape sequence. */ + c = getchar(); +#if (CHIP == M68000) +#ifndef COMPAT + if ((c >= '0') && (c <= '9')) ch = getchar(); + /* ch is either a tilde or a second digit */ +#endif +#endif + switch (c) { + case 'H': return(HO); + case 'A': return(UP); + case 'B': return(DN); + case 'C': return(RT); + case 'D': return(LF); +#if (CHIP == M68000) +#ifndef COMPAT + /* F1 = ESC [ 1 ~ */ + /* F2 = ESC [ 2 ~ */ + /* F3 = ESC [ 3 ~ */ + /* F4 = ESC [ 4 ~ */ + /* F5 = ESC [ 5 ~ */ + /* F6 = ESC [ 6 ~ */ + /* F7 = ESC [ 17 ~ */ + /* F8 = ESC [ 18 ~ */ + case '1': + switch (ch) { + case '~': return(SF); + case '7': (void) getchar(); return(MA); + case '8': (void) getchar(); return(CTL); + } + case '2': return(SR); + case '3': return(PD); + case '4': return(PU); + case '5': return(FS); + case '6': return(EF); +#endif +#endif +#if (CHIP == INTEL) + case 'G': return(FS); + case 'S': return(SR); + case 'T': return(SF); + case 'U': return(PD); + case 'V': return(PU); + case 'Y': return(EF); +#endif + } + return(I); + } +#if (CHIP == M68000) +#ifdef COMPAT + if (c == 'O') { + /* Start of ASCII function key escape sequence. */ + switch (getchar()) { + case 'P': return(SF); + case 'Q': return(SR); + case 'R': return(PD); + case 'S': return(PU); + case 'T': return(FS); + case 'U': return(EF); + case 'V': return(MA); + case 'W': return(CTL); + } + } +#endif +#endif + return(I); +} + +/* + * ESC() wants a count and a command after that. It repeats the + * command count times. If a ^\ is given during repeating, stop looping and + * return to main loop. + */ +void ESC() +{ + register int count = 0; + register void (*func)(); + int index; + + index = getchar(); + while (index >= '0' && index <= '9' && quit == FALSE) { + count *= 10; + count += index - '0'; + index = getchar(); + } + if (count == 0) { + count = 1; + func = escfunc(index); + } else { + func = key_map[index]; + if (func == ESC) + func = escfunc(getchar()); + } + + if (func == I) { /* Function assigned? */ + clear_status(); + return; + } + + while (count-- > 0 && quit == FALSE) { + if (stat_visible == TRUE) + clear_status(); + (*func)(index); + flush(); + } + + if (quit == TRUE) /* Abort has been given */ + error("Aborted", NIL_PTR); +} + +/* + * Ask the user if he wants to save his file or not. + */ +int ask_save() +{ + register int c; + + status_line(file_name[0] ? basename(file_name) : "[buffer]" , + " has been modified. Save? (y/n)"); + + while((c = getchar()) != 'y' && c != 'n' && quit == FALSE) { + ring_bell(); + flush(); + } + + clear_status(); + + if (c == 'y') + return WT(); + + if (c == 'n') + return FINE; + + quit = FALSE; /* Abort character has been given */ + return ERRORS; +} + +/* + * Line_number() finds the line number we're on. + */ +int line_number() +{ + register LINE *line = header->next; + register int count = 1; + + while (line != cur_line) { + count++; + line = line->next; + } + + return count; +} + +/* + * Display a line telling how many chars and lines the file contains. Also tell + * whether the file is readonly and/or modified. + */ +void file_status(message, count, file, lines, writefl, changed) +char *message; +register long count; /* Contains number of characters in file */ +char *file; +int lines; +FLAG writefl, changed; +{ + register LINE *line; + char msg[LINE_LEN + 40];/* Buffer to hold line */ + char yank_msg[LINE_LEN];/* Buffer for msg of yank_file */ + + if (count < 0) /* Not valid. Count chars in file */ + for (line = header->next; line != tail; line = line->next) + count += length_of(line->text); + + if (yank_status != NOT_VALID) /* Append buffer info */ + build_string(yank_msg, " Buffer: %D char%s.", chars_saved, + (chars_saved == 1L) ? "" : "s"); + else + yank_msg[0] = '\0'; + + build_string(msg, "%s %s%s%s %d line%s %D char%s.%s Line %d", message, + (rpipe == TRUE && *message != '[') ? "standard input" : basename(file), + (changed == TRUE) ? "*" : "", + (writefl == FALSE) ? " (Readonly)" : "", + lines, (lines == 1) ? "" : "s", + count, (count == 1L) ? "" : "s", + yank_msg, line_number()); + + if (length_of(msg) + 1 > LINE_LEN - 4) { + msg[LINE_LEN - 4] = SHIFT_MARK; /* Overflow on status line */ + msg[LINE_LEN - 3] = '\0'; + } + status_line(msg, NIL_PTR); /* Print the information */ +} + +/* + * Build_string() prints the arguments as described in fmt, into the buffer. + * %s indicates an argument string, %d indicated an argument number. + */ +#if __STDC__ +void build_string(char *buf, char *fmt, ...) +{ +#else +void build_string(buf, fmt, va_alist) +char *buf, *fmt; +va_dcl +{ +#endif + va_list argptr; + char *scanp; + +#if __STDC__ + va_start(argptr, fmt); +#else + va_start(argptr); +#endif + + while (*fmt) { + if (*fmt == '%') { + fmt++; + switch (*fmt++) { + case 's' : + scanp = va_arg(argptr, char *); + break; + case 'd' : + scanp = num_out((long) va_arg(argptr, int)); + break; + case 'D' : + scanp = num_out((long) va_arg(argptr, long)); + break; + default : + scanp = ""; + } + while (*buf++ = *scanp++) + ; + buf--; + } + else + *buf++ = *fmt++; + } + va_end(argptr); + *buf = '\0'; +} + +/* + * Output an (unsigned) long in a 10 digit field without leading zeros. + * It returns a pointer to the first digit in the buffer. + */ +char *num_out(number) +long number; +{ + static char num_buf[11]; /* Buffer to build number */ + register long digit; /* Next digit of number */ + register long pow = 1000000000L; /* Highest ten power of long */ + FLAG digit_seen = FALSE; + int i; + + for (i = 0; i < 10; i++) { + digit = number / pow; /* Get next digit */ + if (digit == 0L && digit_seen == FALSE && i != 9) + num_buf[i] = ' '; + else { + num_buf[i] = '0' + (char) digit; + number -= digit * pow; /* Erase digit */ + digit_seen = TRUE; + } + pow /= 10L; /* Get next digit */ + } + for (i = 0; num_buf[i] == ' '; i++) /* Skip leading spaces */ + ; + return (&num_buf[i]); +} + +/* + * Get_number() read a number from the terminal. The last character typed in is + * returned. ERRORS is returned on a bad number. The resulting number is put + * into the integer the arguments points to. + */ +int get_number(message, result) +char *message; +int *result; +{ + register int index; + register int count = 0; + + status_line(message, NIL_PTR); + + index = getchar(); + if (quit == FALSE && (index < '0' || index > '9')) { + error("Bad count", NIL_PTR); + return ERRORS; + } + +/* Convert input to a decimal number */ + while (index >= '0' && index <= '9' && quit == FALSE) { + count *= 10; + count += index - '0'; + index = getchar(); + } + + if (quit == TRUE) { + clear_status(); + return ERRORS; + } + + *result = count; + return index; +} + +/* + * Input() reads a string from the terminal. When the KILL character is typed, + * it returns ERRORS. + */ +int input(inbuf, clearfl) +char *inbuf; +FLAG clearfl; +{ + register char *ptr; + register char c; /* Character read */ + + ptr = inbuf; + + *ptr = '\0'; + while (quit == FALSE) { + flush(); + switch (c = getchar()) { + case '\b' : /* Erase previous char */ + if (ptr > inbuf) { + ptr--; +#ifdef UNIX + tputs(SE, 0, _putchar); +#else + string_print(normal_video); +#endif /* UNIX */ + if (is_tab(*ptr)) + string_print(" \b\b\b \b\b"); + else + string_print(" \b\b \b"); +#ifdef UNIX + tputs(SO, 0, _putchar); +#else + string_print(rev_video); +#endif /* UNIX */ + string_print(" \b"); + *ptr = '\0'; + } + else + ring_bell(); + break; + case '\n' : /* End of input */ + /* If inbuf is empty clear status_line */ + return (ptr == inbuf && clearfl == TRUE) ? NO_INPUT :FINE; + default : /* Only read ASCII chars */ + if ((c >= ' ' && c <= '~') || c == '\t') { + *ptr++ = c; + *ptr = '\0'; + if (c == '\t') + string_print("^I"); + else + putchar(c); + string_print(" \b"); + } + else + ring_bell(); + } + } + quit = FALSE; + return ERRORS; +} + +/* + * Get_file() reads a filename from the terminal. Filenames longer than + * FILE_LENGHT chars are truncated. + */ +int get_file(message, file) +char *message, *file; +{ + char *ptr; + int ret; + + if (message == NIL_PTR || (ret = get_string(message, file, TRUE)) == FINE) { + if (length_of((ptr = basename(file))) > NAME_MAX) + ptr[NAME_MAX] = '\0'; + } + return ret; +} + +/* ======================================================================== * + * UNIX I/O Routines * + * ======================================================================== */ + +#ifdef UNIX +#undef putchar + +int _getchar() +{ + char c; + + if (read(input_fd, &c, 1) != 1 && quit == FALSE) + panic ("Cannot read 1 byte from input"); + return c & 0377; +} + +void _flush() +{ + (void) fflush(stdout); +} + +void _putchar(c) +char c; +{ + (void) write_char(STD_OUT, c); +} + +void get_term() +{ + static char termbuf[50]; + extern char *tgetstr(), *getenv(); + char *loc = termbuf; + char entry[1024]; + + if (tgetent(entry, getenv("TERM")) <= 0) { + printf("Unknown terminal.\n"); + exit(1); + } + + AL = tgetstr("al", &loc); + CE = tgetstr("ce", &loc); + VS = tgetstr("vs", &loc); + CL = tgetstr("cl", &loc); + SO = tgetstr("so", &loc); + SE = tgetstr("se", &loc); + CM = tgetstr("cm", &loc); + ymax = tgetnum("li") - 1; + screenmax = ymax - 1; + + if (!CE || !SO || !SE || !CL || !AL || !CM) { + printf("Sorry, no mined on this type of terminal\n"); + exit(1); + } +} +#endif /* UNIX */ diff --git a/bin/mined/mined2.c b/bin/mined/mined2.c new file mode 100644 index 0000000000..8738e63a17 --- /dev/null +++ b/bin/mined/mined2.c @@ -0,0 +1,1725 @@ +/* $DragonFly: src/bin/mined/mined2.c,v 1.1 2005/03/15 01:56:24 dillon Exp $ */ +/* + * Part 2 of the mined editor. + */ + +/* ======================================================================== * + * Move Commands * + * ======================================================================== */ + +#include "mined.h" +#include + +/* + * Move one line up. + */ +void UP() +{ + if (y == 0) { /* Top line of screen. Scroll one line */ + (void) reverse_scroll(); + move_to(x, y); + } + else /* Move to previous line */ + move_to(x, y - 1); +} + +/* + * Move one line down. + */ +void DN() +{ + if (y == last_y) { /* Last line of screen. Scroll one line */ + if (bot_line->next == tail && bot_line->text[0] != '\n') { + dummy_line(); /* Create new empty line */ + DN(); + return; + } + else { + (void) forward_scroll(); + move_to(x, y); + } + } + else /* Move to next line */ + move_to(x, y + 1); +} + +/* + * Move left one position. + */ +void LF() +{ + if (x == 0 && get_shift(cur_line->shift_count) == 0) {/* Begin of line */ + if (cur_line->prev != header) { + UP(); /* Move one line up */ + move_to(LINE_END, y); + } + } + else + move_to(x - 1, y); +} + +/* + * Move right one position. + */ +void RT() +{ + if (*cur_text == '\n') { + if (cur_line->next != tail) { /* Last char of file */ + DN(); /* Move one line down */ + move_to(LINE_START, y); + } + } + else + move_to(x + 1, y); +} + +/* + * Move to coordinates [0, 0] on screen. + */ +void HIGH() +{ + move_to(0, 0); +} + +/* + * Move to coordinates [0, YMAX] on screen. + */ +void LOW() +{ + move_to(0, last_y); +} + +/* + * Move to begin of line. + */ +void BL() +{ + move_to(LINE_START, y); +} + +/* + * Move to end of line. + */ +void EL() +{ + move_to(LINE_END, y); +} + +/* + * GOTO() prompts for a linenumber and moves to that line. + */ +void GOTO() +{ + int number; + LINE *line; + + if (get_number("Please enter line number.", &number) == ERRORS) + return; + + if (number <= 0 || (line = proceed(header->next, number - 1)) == tail) + error("Illegal line number: ", num_out((long) number)); + else + move_to(x, find_y(line)); +} + +/* + * Scroll forward one page or to eof, whatever comes first. (Bot_line becomes + * top_line of display.) Try to leave the cursor on the same line. If this is + * not possible, leave cursor on the line halfway the page. + */ +void PD() +{ + register int i; + + for (i = 0; i < screenmax; i++) + if (forward_scroll() == ERRORS) + break; /* EOF reached */ + if (y - i < 0) /* Line no longer on screen */ + move_to(0, screenmax >> 1); + else + move_to(0, y - i); +} + + +/* + * Scroll backwards one page or to top of file, whatever comes first. (Top_line + * becomes bot_line of display). The very bottom line (YMAX) is always blank. + * Try to leave the cursor on the same line. If this is not possible, leave + * cursor on the line halfway the page. + */ +void PU() +{ + register int i; + + for (i = 0; i < screenmax; i++) + if (reverse_scroll() == ERRORS) + break; /* Top of file reached */ + set_cursor(0, ymax); /* Erase very bottom line */ +#ifdef UNIX + tputs(CE, 0, _putchar); +#else + string_print(blank_line); +#endif /* UNIX */ + if (y + i > screenmax) /* line no longer on screen */ + move_to(0, screenmax >> 1); + else + move_to(0, y + i); +} + +/* + * Go to top of file, scrolling if possible, else redrawing screen. + */ +void HO() +{ + if (proceed(top_line, -screenmax) == header) + PU(); /* It fits. Let PU do it */ + else { + reset(header->next, 0);/* Reset top_line, etc. */ + RD(); /* Display full page */ + } + move_to(LINE_START, 0); +} + +/* + * Go to last line of file, scrolling if possible, else redrawing screen + */ +void EF() +{ + if (tail->prev->text[0] != '\n') + dummy_line(); + if (proceed(bot_line, screenmax) == tail) + PD(); /* It fits. Let PD do it */ + else { + reset(proceed(tail->prev, -screenmax), screenmax); + RD(); /* Display full page */ + } + move_to(LINE_START, last_y); +} + +/* + * Scroll one line up. Leave the cursor on the same line (if possible). + */ +void SU() +{ + if (top_line->prev == header) /* Top of file. Can't scroll */ + return; + + (void) reverse_scroll(); + set_cursor(0, ymax); /* Erase very bottom line */ +#ifdef UNIX + tputs(CE, 0, _putchar); +#else + string_print(blank_line); +#endif /* UNIX */ + move_to(x, (y == screenmax) ? screenmax : y + 1); +} + +/* + * Scroll one line down. Leave the cursor on the same line (if possible). + */ +void SD() +{ + if (forward_scroll() != ERRORS) + move_to(x, (y == 0) ? 0 : y - 1); + else + set_cursor(x, y); +} + +/* + * Perform a forward scroll. It returns ERRORS if we're at the last line of the + * file. + */ +int forward_scroll() +{ + if (bot_line->next == tail) /* Last line of file. No dice */ + return ERRORS; + top_line = top_line->next; + bot_line = bot_line->next; + cur_line = cur_line->next; + set_cursor(0, ymax); + line_print(bot_line); + + return FINE; +} + +/* + * Perform a backwards scroll. It returns ERRORS if we're at the first line + * of the file. + */ +int reverse_scroll() +{ + if (top_line->prev == header) + return ERRORS; /* Top of file. Can't scroll */ + + if (last_y != screenmax) /* Reset last_y if necessary */ + last_y++; + else + bot_line = bot_line->prev; /* Else adjust bot_line */ + top_line = top_line->prev; + cur_line = cur_line->prev; + +/* Perform the scroll */ + set_cursor(0, 0); +#ifdef UNIX + tputs(AL, 0, _putchar); +#else + string_print(rev_scroll); +#endif /* UNIX */ + set_cursor(0, 0); + line_print(top_line); + + return FINE; +} + +/* + * A word is defined as a number of non-blank characters separated by tabs + * spaces or linefeeds. + */ + +/* + * MP() moves to the start of the previous word. A word is defined as a + * number of non-blank characters separated by tabs spaces or linefeeds. + */ +void MP() +{ + move_previous_word(NO_DELETE); +} + +void move_previous_word(remove) +FLAG remove; +{ + register char *begin_line; + register char *textp; + char start_char = *cur_text; + char *start_pos = cur_text; + +/* Fist check if we're at the beginning of line. */ + if (cur_text == cur_line->text) { + if (cur_line->prev == header) + return; + start_char = '\0'; + } + + LF(); + + begin_line = cur_line->text; + textp = cur_text; + +/* Check if we're in the middle of a word. */ + if (!alpha(*textp) || !alpha(start_char)) { + while (textp != begin_line && (white_space(*textp) || *textp == '\n')) + textp--; + } + +/* Now we're at the end of previous word. Skip non-blanks until a blank comes */ + while (textp != begin_line && alpha(*textp)) + textp--; + +/* Go to the next char if we're not at the beginning of the line */ + if (textp != begin_line && *textp != '\n') + textp++; + +/* Find the x-coordinate of this address, and move to it */ + move_address(textp); + if (remove == DELETE) + delete(cur_line, textp, cur_line, start_pos); +} + +/* + * MN() moves to the start of the next word. A word is defined as a number of + * non-blank characters separated by tabs spaces or linefeeds. Always keep in + * mind that the pointer shouldn't pass the '\n'. + */ +void MN() +{ + move_next_word(NO_DELETE); +} + +void move_next_word(remove) +FLAG remove; +{ + register char *textp = cur_text; + +/* Move to the end of the current word. */ + while (*textp != '\n' && alpha(*textp)) + textp++; + +/* Skip all white spaces */ + while (*textp != '\n' && white_space(*textp)) + textp++; +/* If we're deleting. delete the text in between */ + if (remove == DELETE) { + delete(cur_line, cur_text, cur_line, textp); + return; + } + +/* If we're at end of line. move to the first word on the next line. */ + if (*textp == '\n' && cur_line->next != tail) { + DN(); + move_to(LINE_START, y); + textp = cur_text; + while (*textp != '\n' && white_space(*textp)) + textp++; + } + move_address(textp); +} + +/* ======================================================================== * + * Modify Commands * + * ======================================================================== */ + +/* + * DCC deletes the character under the cursor. If this character is a '\n' the + * current line is joined with the next one. + * If this character is the only character of the line, the current line will + * be deleted. + */ +void DCC() +{ + if (*cur_text == '\n') + delete(cur_line,cur_text, cur_line->next,cur_line->next->text); + else + delete(cur_line, cur_text, cur_line, cur_text + 1); +} + +/* + * DPC deletes the character on the left side of the cursor. If the cursor is + * at the beginning of the line, the last character if the previous line is + * deleted. + */ +void DPC() +{ + if (x == 0 && cur_line->prev == header) + return; /* Top of file */ + + LF(); /* Move one left */ + DCC(); /* Delete character under cursor */ +} + +/* + * DLN deletes all characters until the end of the line. If the current + * character is a '\n', then delete that char. + */ +void DLN() +{ + if (*cur_text == '\n') + DCC(); + else + delete(cur_line, cur_text, cur_line, cur_text + length_of(cur_text) -1); +} + +/* + * DNW() deletes the next word (as described in MN()) + */ +void DNW() +{ + if (*cur_text == '\n') + DCC(); + else + move_next_word(DELETE); +} + +/* + * DPW() deletes the next word (as described in MP()) + */ +void DPW() +{ + if (cur_text == cur_line->text) + DPC(); + else + move_previous_word(DELETE); +} + +/* + * Insert character `character' at current location. + */ +void S(character) +register char character; +{ + static char buffer[2]; + + buffer[0] = character; +/* Insert the character */ + if (insert(cur_line, cur_text, buffer) == ERRORS) + return; + +/* Fix screen */ + if (character == '\n') { + set_cursor(0, y); + if (y == screenmax) { /* Can't use display */ + line_print(cur_line); + (void) forward_scroll(); + } + else { + reset(top_line, y); /* Reset pointers */ + display(0, y, cur_line, last_y - y); + } + move_to(0, (y == screenmax) ? y : y + 1); + } + else if (x + 1 == XBREAK)/* If line must be shifted, just call move_to*/ + move_to(x + 1, y); + else { /* else display rest of line */ + put_line(cur_line, x, FALSE); + move_to(x + 1, y); + } +} + +/* + * CTL inserts a control-char at the current location. A message that this + * function is called is displayed at the status line. + */ +void CTL() +{ + register char ctrl; + + status_line("Enter control character.", NIL_PTR); + if ((ctrl = getchar()) >= '\01' && ctrl <= '\037') { + S(ctrl); /* Insert the char */ + clear_status(); + } + else + error ("Unknown control character", NIL_PTR); +} + +/* + * LIB insert a line at the current position and moves back to the end of + * the previous line. + */ +void LIB() +{ + S('\n'); /* Insert the line */ + UP(); /* Move one line up */ + move_to(LINE_END, y); /* Move to end of this line */ +} + +/* + * Line_insert() inserts a new line with text pointed to by `string'. + * It returns the address of the new line. + */ +LINE *line_insert(line, string, len) +register LINE *line; +char *string; +int len; +{ + register LINE *new_line; + +/* Allocate space for LINE structure and text */ + new_line = install_line(string, len); + +/* Install the line into the double linked list */ + new_line->prev = line; + new_line->next = line->next; + line->next = new_line; + new_line->next->prev = new_line; + +/* Increment nlines */ + nlines++; + + return new_line; +} + +/* + * Insert() insert the string `string' at the given line and location. + */ +int insert(line, location, string) +register LINE *line; +char *location, *string; +{ + register char *bufp = text_buffer; /* Buffer for building line */ + register char *textp = line->text; + + if (length_of(textp) + length_of(string) >= MAX_CHARS) { + error("Line too long", NIL_PTR); + return ERRORS; + } + + modified = TRUE; /* File has been modified */ + +/* Copy part of line until `location' has been reached */ + while (textp != location) + *bufp++ = *textp++; + +/* Insert string at this location */ + while (*string != '\0') + *bufp++ = *string++; + *bufp = '\0'; + + if (*(string - 1) == '\n') /* Insert a new line */ + (void) line_insert(line, location, length_of(location)); + else /* Append last part of line */ + copy_string(bufp, location); + +/* Install the new text in this line */ + free_space(line->text); + line->text = alloc(length_of(text_buffer) + 1); + copy_string(line->text, text_buffer); + + return FINE; +} + +/* + * Line_delete() deletes the argument line out of the line list. The pointer to + * the next line is returned. + */ +LINE *line_delete(line) +register LINE *line; +{ + register LINE *next_line = line->next; + +/* Delete the line */ + line->prev->next = line->next; + line->next->prev = line->prev; + +/* Free allocated space */ + free_space(line->text); + free_space((char*)line); + +/* Decrement nlines */ + nlines--; + + return next_line; +} + +/* + * Delete() deletes all the characters (including newlines) between the + * startposition and endposition and fixes the screen accordingly. It + * returns the number of lines deleted. + */ +void delete(start_line, start_textp, end_line, end_textp) +register LINE *start_line; +LINE *end_line; +char *start_textp, *end_textp; +{ + register char *textp = start_line->text; + register char *bufp = text_buffer; /* Storage for new line->text */ + LINE *line, *stop; + int line_cnt = 0; /* Nr of lines deleted */ + int count = 0; + int shift = 0; /* Used in shift calculation */ + int nx = x; + + modified = TRUE; /* File has been modified */ + +/* Set up new line. Copy first part of start line until start_position. */ + while (textp < start_textp) { + *bufp++ = *textp++; + count++; + } + +/* Check if line doesn't exceed MAX_CHARS */ + if (count + length_of(end_textp) >= MAX_CHARS) { + error("Line too long", NIL_PTR); + return; + } + +/* Copy last part of end_line if end_line is not tail */ + copy_string(bufp, (end_textp != NIL_PTR) ? end_textp : "\n"); + +/* Delete all lines between start and end_position (including end_line) */ + line = start_line->next; + stop = end_line->next; + while (line != stop && line != tail) { + line = line_delete(line); + line_cnt++; + } + +/* Check if last line of file should be deleted */ + if (end_textp == NIL_PTR && length_of(start_line->text) == 1 && nlines > 1) { + start_line = start_line->prev; + (void) line_delete(start_line->next); + line_cnt++; + } + else { /* Install new text */ + free_space(start_line->text); + start_line->text = alloc(length_of(text_buffer) + 1); + copy_string(start_line->text, text_buffer); + } + +/* Fix screen. First check if line is shifted. Perhaps we should shift it back*/ + if (get_shift(start_line->shift_count)) { + shift = (XBREAK - count_chars(start_line)) / SHIFT_SIZE; + if (shift > 0) { /* Shift line `shift' back */ + if (shift >= get_shift(start_line->shift_count)) + start_line->shift_count = 0; + else + start_line->shift_count -= shift; + nx += shift * SHIFT_SIZE;/* Reset x value */ + } + } + + if (line_cnt == 0) { /* Check if only one line changed */ + if (shift > 0) { /* Reprint whole line */ + set_cursor(0, y); + line_print(start_line); + } + else { /* Just display last part of line */ + set_cursor(x, y); + put_line(start_line, x, TRUE); + } + move_to(nx, y); /* Reset cur_text */ + return; + } + + shift = last_y; /* Save value */ + reset(top_line, y); + display(0, y, start_line, shift - y); + move_to((line_cnt == 1) ? nx : 0, y); +} + +/* ======================================================================== * + * Yank Commands * + * ======================================================================== */ + +LINE *mark_line; /* For marking position. */ +char *mark_text; +int lines_saved; /* Nr of lines in buffer */ + +/* + * PT() inserts the buffer at the current location. + */ +void PT() +{ + register int fd; /* File descriptor for buffer */ + + if ((fd = scratch_file(READ)) == ERRORS) + error("Buffer is empty.", NIL_PTR); + else { + file_insert(fd, FALSE);/* Insert the buffer */ + (void) close(fd); + } +} + +/* + * IF() prompt for a filename and inserts the file at the current location + * in the file. + */ +void IF() +{ + register int fd; /* File descriptor of file */ + char name[LINE_LEN]; /* Buffer for file name */ + +/* Get the file name */ + if (get_file("Get and insert file:", name) != FINE) + return; + + if ((fd = open(name, 0)) < 0) + error("Cannot open ", name); + else { + file_insert(fd, TRUE); /* Insert the file */ + (void) close(fd); + } +} + +/* + * File_insert() inserts a an opened file (as given by filedescriptor fd) + * at the current location. + */ +void file_insert(fd, old_pos) +int fd; +FLAG old_pos; +{ + char line_buffer[MAX_CHARS]; /* Buffer for next line */ + register LINE *line = cur_line; + register int line_count = nlines; /* Nr of lines inserted */ + LINE *page = cur_line; + int ret = ERRORS; + +/* Get the first piece of text (might be ended with a '\n') from fd */ + if (get_line(fd, line_buffer) == ERRORS) + return; /* Empty file */ + +/* Insert this text at the current location. */ + if (insert(line, cur_text, line_buffer) == ERRORS) + return; + +/* Repeat getting lines (and inserting lines) until EOF is reached */ + while ((ret = get_line(fd, line_buffer)) != ERRORS && ret != NO_LINE) + line = line_insert(line, line_buffer, ret); + + if (ret == NO_LINE) { /* Last line read not ended by a '\n' */ + line = line->next; + (void) insert(line, line->text, line_buffer); + } + +/* Calculate nr of lines added */ + line_count = nlines - line_count; + +/* Fix the screen */ + if (line_count == 0) { /* Only one line changed */ + set_cursor(0, y); + line_print(line); + move_to((old_pos == TRUE) ? x : x + length_of(line_buffer), y); + } + else { /* Several lines changed */ + reset(top_line, y); /* Reset pointers */ + while (page != line && page != bot_line->next) + page = page->next; + if (page != bot_line->next || old_pos == TRUE) + display(0, y, cur_line, screenmax - y); + if (old_pos == TRUE) + move_to(x, y); + else if (ret == NO_LINE) + move_to(length_of(line_buffer), find_y(line)); + else + move_to(0, find_y(line->next)); + } + +/* If nr of added line >= REPORT, print the count */ + if (line_count >= REPORT) + status_line(num_out((long) line_count), " lines added."); +} + +/* + * WB() writes the buffer (yank_file) into another file, which + * is prompted for. + */ +void WB() +{ + register int new_fd; /* Filedescriptor to copy file */ + int yank_fd; /* Filedescriptor to buffer */ + register int cnt; /* Count check for read/write */ + int ret = 0; /* Error check for write */ + char file[LINE_LEN]; /* Output file */ + +/* Checkout the buffer */ + if ((yank_fd = scratch_file(READ)) == ERRORS) { + error("Buffer is empty.", NIL_PTR); + return; + } + +/* Get file name */ + if (get_file("Write buffer to file:", file) != FINE) + return; + +/* Creat the new file */ + if ((new_fd = creat(file, 0644)) < 0) { + error("Cannot create ", file); + return; + } + + status_line("Writing ", file); + +/* Copy buffer into file */ + while ((cnt = read(yank_fd, text_buffer, sizeof(text_buffer))) > 0) + if (write(new_fd, text_buffer, cnt) != cnt) { + bad_write(new_fd); + ret = ERRORS; + break; + } + +/* Clean up open files and status_line */ + (void) close(new_fd); + (void) close(yank_fd); + + if (ret != ERRORS) /* Bad write */ + file_status("Wrote", chars_saved, file, lines_saved, TRUE, FALSE); +} + +/* + * MA sets mark_line (mark_text) to the current line (text pointer). + */ +void MA() +{ + mark_line = cur_line; + mark_text = cur_text; + status_line("Mark set", NIL_PTR); +} + +/* + * YA() puts the text between the marked position and the current + * in the buffer. + */ +void YA() +{ + set_up(NO_DELETE); +} + +/* + * DT() is essentially the same as YA(), but in DT() the text is deleted. + */ +void DT() +{ + set_up(DELETE); +} + +/* + * Set_up is an interface to the actual yank. It calls checkmark () to check + * if the marked position is still valid. If it is, yank is called with the + * arguments in the right order. + */ +void set_up(remove) +FLAG remove; /* DELETE if text should be deleted */ +{ + switch (checkmark()) { + case NOT_VALID : + error("Mark not set.", NIL_PTR); + return; + case SMALLER : + yank(mark_line, mark_text, cur_line, cur_text, remove); + break; + case BIGGER : + yank(cur_line, cur_text, mark_line, mark_text, remove); + break; + case SAME : /* Ignore stupid behaviour */ + yank_status = EMPTY; + chars_saved = 0L; + status_line("0 characters saved in buffer.", NIL_PTR); + break; + } +} + +/* + * Check_mark() checks if mark_line and mark_text are still valid pointers. If + * they are it returns SMALLER if the marked position is before the current, + * BIGGER if it isn't or SAME if somebody didn't get the point. + * NOT_VALID is returned when mark_line and/or mark_text are no longer valid. + * Legal() checks if mark_text is valid on the mark_line. + */ +FLAG checkmark() +{ + register LINE *line; + FLAG cur_seen = FALSE; + +/* Special case: check is mark_line and cur_line are the same. */ + if (mark_line == cur_line) { + if (mark_text == cur_text) /* Even same place */ + return SAME; + if (legal() == ERRORS) /* mark_text out of range */ + return NOT_VALID; + return (mark_text < cur_text) ? SMALLER : BIGGER; + } + +/* Start looking for mark_line in the line structure */ + for (line = header->next; line != tail; line = line->next) { + if (line == cur_line) + cur_seen = TRUE; + else if (line == mark_line) + break; + } + +/* If we found mark_line (line != tail) check for legality of mark_text */ + if (line == tail || legal() == ERRORS) + return NOT_VALID; + +/* cur_seen is TRUE if cur_line is before mark_line */ + return (cur_seen == TRUE) ? BIGGER : SMALLER; +} + +/* + * Legal() checks if mark_text is still a valid pointer. + */ +int legal() +{ + register char *textp = mark_line->text; + +/* Locate mark_text on mark_line */ + while (textp != mark_text && *textp++ != '\0') + ; + return (*textp == '\0') ? ERRORS : FINE; +} + +/* + * Yank puts all the text between start_position and end_position into + * the buffer. + * The caller must check that the arguments to yank() are valid. (E.g. in + * the right order) + */ +void yank(start_line, start_textp, end_line, end_textp, remove) +LINE *start_line, *end_line; +char *start_textp, *end_textp; +FLAG remove; /* DELETE if text should be deleted */ +{ + register LINE *line = start_line; + register char *textp = start_textp; + int fd; + +/* Creat file to hold buffer */ + if ((fd = scratch_file(WRITE)) == ERRORS) + return; + + chars_saved = 0L; + lines_saved = 0; + status_line("Saving text.", NIL_PTR); + +/* Keep writing chars until the end_location is reached. */ + while (textp != end_textp) { + if (write_char(fd, *textp) == ERRORS) { + (void) close(fd); + return; + } + if (*textp++ == '\n') { /* Move to the next line */ + line = line->next; + textp = line->text; + lines_saved++; + } + chars_saved++; + } + +/* Flush the I/O buffer and close file */ + if (flush_buffer(fd) == ERRORS) { + (void) close(fd); + return; + } + (void) close(fd); + yank_status = VALID; + +/* + * Check if the text should be deleted as well. If it should, the following + * hack is used to save a lot of code. First move back to the start_position. + * (This might be the location we're on now!) and them delete the text. + * It might be a bit confusing the first time somebody uses it. + * Delete() will fix the screen. + */ + if (remove == DELETE) { + move_to(find_x(start_line, start_textp), find_y(start_line)); + delete(start_line, start_textp, end_line, end_textp); + } + + status_line(num_out(chars_saved), " characters saved in buffer."); +} + +/* + * Scratch_file() creates a uniq file in /usr/tmp. If the file couldn't + * be created other combinations of files are tried until a maximum + * of MAXTRAILS times. After MAXTRAILS times, an error message is given + * and ERRORS is returned. + */ + +#define MAXTRAILS 26 + +int scratch_file(mode) +FLAG mode; /* Can be READ or WRITE permission */ +{ + static int trials = 0; /* Keep track of trails */ + register char *y_ptr, *n_ptr; + int fd; /* Filedescriptor to buffer */ + +/* If yank_status == NOT_VALID, scratch_file is called for the first time */ + if (yank_status == NOT_VALID && mode == WRITE) { /* Create new file */ + /* Generate file name. */ + y_ptr = &yank_file[11]; + n_ptr = num_out((long) getpid()); + while ((*y_ptr = *n_ptr++) != '\0') + y_ptr++; + *y_ptr++ = 'a' + trials; + *y_ptr = '\0'; + /* Check file existence */ + if (access(yank_file, 0) == 0 || (fd = creat(yank_file, 0644)) < 0) { + if (trials++ >= MAXTRAILS) { + error("Unable to creat scratchfile.", NIL_PTR); + return ERRORS; + } + else + return scratch_file(mode);/* Have another go */ + } + } + else if ((mode == READ && (fd = open(yank_file, 0)) < 0) || + (mode == WRITE && (fd = creat(yank_file, 0644)) < 0)) { + yank_status = NOT_VALID; + return ERRORS; + } + + clear_buffer(); + return fd; +} + +/* ======================================================================== * + * Search Routines * + * ======================================================================== */ + +/* + * A regular expression consists of a sequence of: + * 1. A normal character matching that character. + * 2. A . matching any character. + * 3. A ^ matching the begin of a line. + * 4. A $ (as last character of the pattern) mathing the end of a line. + * 5. A \ matching . + * 6. A number of characters enclosed in [] pairs matching any of these + * characters. A list of characters can be indicated by a '-'. So + * [a-z] matches any letter of the alphabet. If the first character + * after the '[' is a '^' then the set is negated (matching none of + * the characters). + * A ']', '^' or '-' can be escaped by putting a '\' in front of it. + * 7. If one of the expressions as described in 1-6 is followed by a + * '*' than that expressions matches a sequence of 0 or more of + * that expression. + */ + +char typed_expression[LINE_LEN]; /* Holds previous expr. */ + +/* + * SF searches forward for an expression. + */ +void SF() +{ + search("Search forward:", FORWARD); +} + +/* + * SF searches backwards for an expression. + */ +void SR() +{ + search("Search reverse:", REVERSE); +} + +/* + * Get_expression() prompts for an expression. If just a return is typed, the + * old expression is used. If the expression changed, compile() is called and + * the returning REGEX structure is returned. It returns NIL_REG upon error. + * The save flag indicates whether the expression should be appended at the + * message pointer. + */ +REGEX *get_expression(message) +char *message; +{ + static REGEX program; /* Program of expression */ + char exp_buf[LINE_LEN]; /* Buffer for new expr. */ + + if (get_string(message, exp_buf, FALSE) == ERRORS) + return NIL_REG; + + if (exp_buf[0] == '\0' && typed_expression[0] == '\0') { + error("No previous expression.", NIL_PTR); + return NIL_REG; + } + + if (exp_buf[0] != '\0') { /* A new expr. is typed */ + copy_string(typed_expression, exp_buf);/* Save expr. */ + compile(exp_buf, &program); /* Compile new expression */ + } + + if (program.status == REG_ERROR) { /* Error during compiling */ + error(program.result.err_mess, NIL_PTR); + return NIL_REG; + } + return &program; +} + +/* + * GR() a replaces all matches from the current position until the end + * of the file. + */ +void GR() +{ + change("Global replace:", VALID); +} + +/* + * LR() replaces all matches on the current line. + */ +void LR() +{ + change("Line replace:", NOT_VALID); +} + +/* + * Change() prompts for an expression and a substitution pattern and changes + * all matches of the expression into the substitution. change() start looking + * for expressions at the current line and continues until the end of the file + * if the FLAG file is VALID. + */ +void change(message, file) +char *message; /* Message to prompt for expression */ +FLAG file; +{ + char mess_buf[LINE_LEN]; /* Buffer to hold message */ + char replacement[LINE_LEN]; /* Buffer to hold subst. pattern */ + REGEX *program; /* Program resulting from compilation */ + register LINE *line = cur_line; + register char *textp; + long lines = 0L; /* Nr of lines on which subs occurred */ + long subs = 0L; /* Nr of subs made */ + int page = y; /* Index to check if line is on screen*/ + +/* Save message and get expression */ + copy_string(mess_buf, message); + if ((program = get_expression(mess_buf)) == NIL_REG) + return; + +/* Get substitution pattern */ + build_string(mess_buf, "%s %s by:", mess_buf, typed_expression); + if (get_string(mess_buf, replacement, FALSE) == ERRORS) + return; + + set_cursor(0, ymax); + flush(); +/* Substitute until end of file */ + do { + if (line_check(program, line->text, FORWARD)) { + lines++; + /* Repeat sub. on this line as long as we find a match*/ + do { + subs++; /* Increment subs */ + if ((textp = substitute(line, program,replacement)) + == NIL_PTR) + return; /* Line too long */ + } while ((program->status & BEGIN_LINE) != BEGIN_LINE && + (program->status & END_LINE) != END_LINE && + line_check(program, textp, FORWARD)); + /* Check to see if we can print the result */ + if (page <= screenmax) { + set_cursor(0, page); + line_print(line); + } + } + if (page <= screenmax) + page++; + line = line->next; + } while (line != tail && file == VALID && quit == FALSE); + + copy_string(mess_buf, (quit == TRUE) ? "(Aborted) " : ""); +/* Fix the status line */ + if (subs == 0L && quit == FALSE) + error("Pattern not found.", NIL_PTR); + else if (lines >= REPORT || quit == TRUE) { + build_string(mess_buf, "%s %D substitutions on %D lines.", mess_buf, + subs, lines); + status_line(mess_buf, NIL_PTR); + } + else if (file == NOT_VALID && subs >= REPORT) + status_line(num_out(subs), " substitutions."); + else + clear_status(); + move_to (x, y); +} + +/* + * Substitute() replaces the match on this line by the substitute pattern + * as indicated by the program. Every '&' in the replacement is replaced by + * the original match. A \ in the replacement escapes the next character. + */ +char *substitute(line, program, replacement) +LINE *line; +REGEX *program; +char *replacement; /* Contains replacement pattern */ +{ + register char *textp = text_buffer; + register char *subp = replacement; + char *linep = line->text; + char *amp; + + modified = TRUE; + +/* Copy part of line until the beginning of the match */ + while (linep != program->start_ptr) + *textp++ = *linep++; + +/* + * Replace the match by the substitution pattern. Each occurrence of '&' is + * replaced by the original match. A \ escapes the next character. + */ + while (*subp != '\0' && textp < &text_buffer[MAX_CHARS]) { + if (*subp == '&') { /* Replace the original match */ + amp = program->start_ptr; + while (amp < program->end_ptr && textp<&text_buffer[MAX_CHARS]) + *textp++ = *amp++; + subp++; + } + else { + if (*subp == '\\' && *(subp + 1) != '\0') + subp++; + *textp++ = *subp++; + } + } + +/* Check for line length not exceeding MAX_CHARS */ + if (length_of(text_buffer) + length_of(program->end_ptr) >= MAX_CHARS) { + error("Substitution result: line too big", NIL_PTR); + return NIL_PTR; + } + +/* Append last part of line to the new build line */ + copy_string(textp, program->end_ptr); + +/* Free old line and install new one */ + free_space(line->text); + line->text = alloc(length_of(text_buffer) + 1); + copy_string(line->text, text_buffer); + + return(line->text + (textp - text_buffer)); +} + +/* + * Search() calls get_expression to fetch the expression. If this went well, + * the function match() is called which returns the line with the next match. + * If this line is the NIL_LINE, it means that a match could not be found. + * Find_x() and find_y() display the right page on the screen, and return + * the right coordinates for x and y. These coordinates are passed to move_to() + */ +void search(message, method) +char *message; +FLAG method; +{ + register REGEX *program; + register LINE *match_line; + +/* Get the expression */ + if ((program = get_expression(message)) == NIL_REG) + return; + + set_cursor(0, ymax); + flush(); +/* Find the match */ + if ((match_line = match(program, cur_text, method)) == NIL_LINE) { + if (quit == TRUE) + status_line("Aborted", NIL_PTR); + else + status_line("Pattern not found.", NIL_PTR); + return; + } + + move(0, program->start_ptr, find_y(match_line)); + clear_status(); +} + +/* + * find_y() checks if the matched line is on the current page. If it is, it + * returns the new y coordinate, else it displays the correct page with the + * matched line in the middle and returns the new y value; + */ +int find_y(match_line) +LINE *match_line; +{ + register LINE *line; + register int count = 0; + +/* Check if match_line is on the same page as currently displayed. */ + for (line = top_line; line != match_line && line != bot_line->next; + line = line->next) + count++; + if (line != bot_line->next) + return count; + +/* Display new page, with match_line in center. */ + if ((line = proceed(match_line, -(screenmax >> 1))) == header) { + /* Can't display in the middle. Make first line of file top_line */ + count = 0; + for (line = header->next; line != match_line; line = line->next) + count++; + line = header->next; + } + else /* New page is displayed. Set cursor to middle of page */ + count = screenmax >> 1; + +/* Reset pointers and redraw the screen */ + reset(line, 0); + RD(); + + return count; +} + +/* Opcodes for characters */ +#define NORMAL 0x0200 +#define DOT 0x0400 +#define EOLN 0x0800 +#define STAR 0x1000 +#define BRACKET 0x2000 +#define NEGATE 0x0100 +#define DONE 0x4000 + +/* Mask for opcodes and characters */ +#define LOW_BYTE 0x00FF +#define HIGH_BYTE 0xFF00 + +/* Previous is the contents of the previous address (ptr) points to */ +#define previous(ptr) (*((ptr) - 1)) + +/* Buffer to store outcome of compilation */ +int exp_buffer[BLOCK_SIZE]; + +/* Errors often used */ +char *too_long = "Regular expression too long"; + +/* + * Reg_error() is called by compile() is something went wrong. It set the + * status of the structure to error, and assigns the error field of the union. + */ +#define reg_error(str) program->status = REG_ERROR, \ + program->result.err_mess = (str) +/* + * Finished() is called when everything went right during compilation. It + * allocates space for the expression, and copies the expression buffer into + * this field. + */ +void finished(program, last_exp) +register REGEX *program; +int *last_exp; +{ + register int length = (last_exp - exp_buffer) * sizeof(int); + +/* Allocate space */ + program->result.expression = (int *) alloc(length); +/* Copy expression. (expression consists of ints!) */ + bcopy(exp_buffer, program->result.expression, length); +} + +/* + * Compile compiles the pattern into a more comprehensible form and returns a + * REGEX structure. If something went wrong, the status field of the structure + * is set to REG_ERROR and an error message is set into the err_mess field of + * the union. If all went well the expression is saved and the expression + * pointer is set to the saved (and compiled) expression. + */ +void compile(pattern, program) +register char *pattern; /* Pointer to pattern */ +REGEX *program; +{ + register int *expression = exp_buffer; + int *prev_char; /* Pointer to previous compiled atom */ + int *acct_field; /* Pointer to last BRACKET start */ + FLAG negate; /* Negate flag for BRACKET */ + char low_char; /* Index for chars in BRACKET */ + char c; + +/* Check for begin of line */ + if (*pattern == '^') { + program->status = BEGIN_LINE; + pattern++; + } + else { + program->status = 0; +/* If the first character is a '*' we have to assign it here. */ + if (*pattern == '*') { + *expression++ = '*' + NORMAL; + pattern++; + } + } + + for (; ;) { + switch (c = *pattern++) { + case '.' : + *expression++ = DOT; + break; + case '$' : + /* + * Only means EOLN if it is the last char of the pattern + */ + if (*pattern == '\0') { + *expression++ = EOLN | DONE; + program->status |= END_LINE; + finished(program, expression); + return; + } + else + *expression++ = NORMAL + '$'; + break; + case '\0' : + *expression++ = DONE; + finished(program, expression); + return; + case '\\' : + /* If last char, it must! mean a normal '\' */ + if (*pattern == '\0') + *expression++ = NORMAL + '\\'; + else + *expression++ = NORMAL + *pattern++; + break; + case '*' : + /* + * If the previous expression was a [] find out the + * begin of the list, and adjust the opcode. + */ + prev_char = expression - 1; + if (*prev_char & BRACKET) + *(expression - (*acct_field & LOW_BYTE))|= STAR; + else + *prev_char |= STAR; + break; + case '[' : + /* + * First field in expression gives information about + * the list. + * The opcode consists of BRACKET and if necessary + * NEGATE to indicate that the list should be negated + * and/or STAR to indicate a number of sequence of this + * list. + * The lower byte contains the length of the list. + */ + acct_field = expression++; + if (*pattern == '^') { /* List must be negated */ + pattern++; + negate = TRUE; + } + else + negate = FALSE; + while (*pattern != ']') { + if (*pattern == '\0') { + reg_error("Missing ]"); + return; + } + if (*pattern == '\\') + pattern++; + *expression++ = *pattern++; + if (*pattern == '-') { + /* Make list of chars */ + low_char = previous(pattern); + pattern++; /* Skip '-' */ + if (low_char++ > *pattern) { + reg_error("Bad range in [a-z]"); + return; + } + /* Build list */ + while (low_char <= *pattern) + *expression++ = low_char++; + pattern++; + } + if (expression >= &exp_buffer[BLOCK_SIZE]) { + reg_error(too_long); + return; + } + } + pattern++; /* Skip ']' */ + /* Assign length of list in acct field */ + if ((*acct_field = (expression - acct_field)) == 1) { + reg_error("Empty []"); + return; + } + /* Assign negate and bracket field */ + *acct_field |= BRACKET; + if (negate == TRUE) + *acct_field |= NEGATE; + /* + * Add BRACKET to opcode of last char in field because + * a '*' may be following the list. + */ + previous(expression) |= BRACKET; + break; + default : + *expression++ = c + NORMAL; + } + if (expression == &exp_buffer[BLOCK_SIZE]) { + reg_error(too_long); + return; + } + } + /* NOTREACHED */ +} + +/* + * Match gets as argument the program, pointer to place in current line to + * start from and the method to search for (either FORWARD or REVERSE). + * Match() will look through the whole file until a match is found. + * NIL_LINE is returned if no match could be found. + */ +LINE *match(program, string, method) +REGEX *program; +char *string; +register FLAG method; +{ + register LINE *line = cur_line; + char old_char; /* For saving chars */ + +/* Corrupted program */ + if (program->status == REG_ERROR) + return NIL_LINE; + +/* Check part of text first */ + if (!(program->status & BEGIN_LINE)) { + if (method == FORWARD) { + if (line_check(program, string + 1, method) == MATCH) + return cur_line; /* Match found */ + } + else if (!(program->status & END_LINE)) { + old_char = *string; /* Save char and */ + *string = '\n'; /* Assign '\n' for line_check */ + if (line_check(program, line->text, method) == MATCH) { + *string = old_char; /* Restore char */ + return cur_line; /* Found match */ + } + *string = old_char; /* No match, but restore char */ + } + } + +/* No match in last (or first) part of line. Check out rest of file */ + do { + line = (method == FORWARD) ? line->next : line->prev; + if (line->text == NIL_PTR) /* Header/tail */ + continue; + if (line_check(program, line->text, method) == MATCH) + return line; + } while (line != cur_line && quit == FALSE); + +/* No match found. */ + return NIL_LINE; +} + +/* + * Line_check() checks the line (or rather string) for a match. Method + * indicates FORWARD or REVERSE search. It scans through the whole string + * until a match is found, or the end of the string is reached. + */ +int line_check(program, string, method) +register REGEX *program; +char *string; +FLAG method; +{ + register char *textp = string; + +/* Assign start_ptr field. We might find a match right away! */ + program->start_ptr = textp; + +/* If the match must be anchored, just check the string. */ + if (program->status & BEGIN_LINE) + return check_string(program, string, NIL_INT); + + if (method == REVERSE) { + /* First move to the end of the string */ + for (textp = string; *textp != '\n'; textp++) + ; + /* Start checking string until the begin of the string is met */ + while (textp >= string) { + program->start_ptr = textp; + if (check_string(program, textp--, NIL_INT)) + return MATCH; + } + } + else { + /* Move through the string until the end of is found */ + while (quit == FALSE && *textp != '\0') { + program->start_ptr = textp; + if (check_string(program, textp, NIL_INT)) + return MATCH; + if (*textp == '\n') + break; + textp++; + } + } + + return NO_MATCH; +} + +/* + * Check() checks of a match can be found in the given string. Whenever a STAR + * is found during matching, then the begin position of the string is marked + * and the maximum number of matches is performed. Then the function star() + * is called which starts to finish the match from this position of the string + * (and expression). Check() return MATCH for a match, NO_MATCH is the string + * couldn't be matched or REG_ERROR for an illegal opcode in expression. + */ +int check_string(program, string, expression) +REGEX *program; +register char *string; +int *expression; +{ + register int opcode; /* Holds opcode of next expr. atom */ + char c; /* Char that must be matched */ + char *mark; /* For marking position */ + int star_fl; /* A star has been born */ + + if (expression == NIL_INT) + expression = program->result.expression; + +/* Loop until end of string or end of expression */ + while (quit == FALSE && !(*expression & DONE) && + *string != '\0' && *string != '\n') { + c = *expression & LOW_BYTE; /* Extract match char */ + opcode = *expression & HIGH_BYTE; /* Extract opcode */ + if (star_fl = (opcode & STAR)) { /* Check star occurrence */ + opcode &= ~STAR; /* Strip opcode */ + mark = string; /* Mark current position */ + } + expression++; /* Increment expr. */ + switch (opcode) { + case NORMAL : + if (star_fl) + while (*string++ == c) /* Skip all matches */ + ; + else if (*string++ != c) + return NO_MATCH; + break; + case DOT : + string++; + if (star_fl) /* Skip to eoln */ + while (*string != '\0' && *string++ != '\n') + ; + break; + case NEGATE | BRACKET: + case BRACKET : + if (star_fl) + while (in_list(expression, *string++, c, opcode) + == MATCH) + ; + else if (in_list(expression, *string++, c, opcode) == NO_MATCH) + return NO_MATCH; + expression += c - 1; /* Add length of list */ + break; + default : + panic("Corrupted program in check_string()"); + } + if (star_fl) + return star(program, mark, string, expression); + } + if (*expression & DONE) { + program->end_ptr = string; /* Match ends here */ + /* + * We might have found a match. The last thing to do is check + * whether a '$' was given at the end of the expression, or + * the match was found on a null string. (E.g. [a-z]* always + * matches) unless a ^ or $ was included in the pattern. + */ + if ((*expression & EOLN) && *string != '\n' && *string != '\0') + return NO_MATCH; + if (string == program->start_ptr && !(program->status & BEGIN_LINE) + && !(*expression & EOLN)) + return NO_MATCH; + return MATCH; + } + return NO_MATCH; +} + +/* + * Star() calls check_string() to find out the longest match possible. + * It searches backwards until the (in check_string()) marked position + * is reached, or a match is found. + */ +int star(program, end_position, string, expression) +REGEX *program; +register char *end_position; +register char *string; +int *expression; +{ + do { + string--; + if (check_string(program, string, expression)) + return MATCH; + } while (string != end_position); + + return NO_MATCH; +} + +/* + * In_list() checks if the given character is in the list of []. If it is + * it returns MATCH. if it isn't it returns NO_MATCH. These returns values + * are reversed when the NEGATE field in the opcode is present. + */ +int in_list(list, c, list_length, opcode) +register int *list; +char c; +register int list_length; +int opcode; +{ + if (c == '\0' || c == '\n') /* End of string, never matches */ + return NO_MATCH; + while (list_length-- > 1) { /* > 1, don't check acct_field */ + if ((*list & LOW_BYTE) == c) + return (opcode & NEGATE) ? NO_MATCH : MATCH; + list++; + } + return (opcode & NEGATE) ? MATCH : NO_MATCH; +} + +/* + * Dummy_line() adds an empty line at the end of the file. This is sometimes + * useful in combination with the EF and DN command in combination with the + * Yank command set. + */ +void dummy_line() +{ + (void) line_insert(tail->prev, "\n", 1); + tail->prev->shift_count = DUMMY; + if (last_y != screenmax) { + last_y++; + bot_line = bot_line->next; + } +} -- 2.41.0