[comp.sources.misc] VI in TPU part 10/13

gregg@a.cs.okstate.edu@mandrill.CWRU.Edu (Gregg Wonderly) (10/13/87)

$ WRITE SYS$OUTPUT "Creating ""VI.9"""
$ CREATE VI.9
$ DECK/DOLLARS=$$EOD$$
    vi$active_count := 0;
ENDPROCEDURE;

!
!   Move to line in file.  vi$active_count holds the line number to GO TO.
!   If VI$ACTIVE_COUNT is zero, we move to the end of the file.
!
PROCEDURE vi$to_line (cnt)

    LOCAL
        this_pos,           ! Saved position in case of botch
        last_line,          ! Last line in the buffer
        win_len;            ! Length of CURRENT_WINDOW

    ON_ERROR
        vi$message (FAO ("No such line: !SL", VI$ACTIVE_COUNT));
        POSITION (this_pos);
        cnt := 0;
        RETURN;
    ENDON_ERROR;

    this_pos := MARK(NONE);
    MOVE_HORIZONTAL (-CURRENT_OFFSET);
    vi$start_pos := MARK (NONE);

    IF cnt = 0 THEN
        POSITION (END_OF (CURRENT_BUFFER));
    ELSE
        last_line := GET_INFO (CURRENT_BUFFER, "RECORD_COUNT");

        IF cnt > last_line THEN
            IF last_line > 0 THEN
                vi$message ("Not that many lines in buffer");
                POSITION (this_pos);
                RETURN (0);
            ENDIF;
        ELSE
            POSITION (BEGINNING_OF (CURRENT_BUFFER));
            win_len := GET_INFO (CURRENT_WINDOW, "VISIBLE_LENGTH");
            MOVE_VERTICAL (cnt - 1);
            cnt := 0;
        ENDIF;
    ENDIF;

    IF (MARK (NONE) <> END_OF (CURRENT_BUFFER)) THEN
        MOVE_VERTICAL (1);
        MOVE_HORIZONTAL (-1);
        vi$new_endpos := MARK (NONE);
        MOVE_HORIZONTAL (-CURRENT_OFFSET);
    ENDIF;

    vi$yank_mode := VI$LINE_MODE;
    RETURN (vi$retpos (this_pos));
ENDPROCEDURE;

!
!   Set a marker in the current buffer.
!
PROCEDURE vi$_set_mark

    LOCAL
        mark_char,
        mark_name,
        key_pressed;

    key_pressed := vi$read_a_key;

    mark_char := ASCII (key_pressed);
    IF (INDEX (vi$_lower_chars, mark_char) <> 0) THEN
        mark_name := "vi$mark_" + mark_char;
        EXECUTE (COMPILE (mark_name + " := MARK(NONE);"));
    ELSE
        vi$MESSAGE ("Invalid marker key!");
    ENDIF;

ENDPROCEDURE;

!
!   Function mapped to "'" and "`".
!
PROCEDURE vi$_go_to_marker
    IF (vi$position (vi$to_marker, 1) <> 0) THEN
        vi$pos_in_middle (MARK (NONE));
    ENDIF;
ENDPROCEDURE;

!
!   Function to move the marker indicated by the next keystroke.
!
PROCEDURE vi$to_marker

    LOCAL
        mode_key,
        pos,
        mark_name,
        mark_char,
        key_pressed;

    ON_ERROR;
        vi$message ("Mark not set!");
        RETURN (0);
    ENDON_ERROR;

    pos := MARK (NONE);
    mode_key := vi$last_key;
    key_pressed := vi$read_a_key;

    mark_char := ASCII (key_pressed);
    IF (INDEX (vi$_lower_chars+"'`", mark_char) = 0) THEN
        vi$MESSAGE ("Invalid marker key!");
        RETURN (0);
    ENDIF;

    IF (key_pressed <> F11) THEN
        IF (mark_char = "'") OR (mark_char = "`") THEN
            IF (vi$old_place <> 0) THEN
                IF (GET_INFO (vi$old_place, "BUFFER") = CURRENT_BUFFER) THEN
                    POSITION (vi$old_place);
                ELSE
                    MESSAGE ("Previous place not in this buffer!");
                    RETURN (0);
                ENDIF;
            ELSE
                MESSAGE ("No previous mark to return to!");
                RETURN (0);
            ENDIF;
        ELSE
            mark_name := "vi$mark_" + mark_char;
            EXECUTE (COMPILE ("vi$global_mark := "+mark_name+";"));

            IF (vi$global_mark <> 0) AND (GET_INFO (vi$global_mark, "BUFFER") =
                                                            CURRENT_BUFFER) THE
N
                POSITION (vi$global_mark);
                vi$yank_mode := VI$LINE_MODE;
            ELSE
                vi$message ("Invalid mark for this buffer!");
                RETURN (0);
            ENDIF;
        ENDIF;

        IF ASCII (mode_key) = "'" THEN
            MOVE_HORIZONTAL (-CURRENT_OFFSET);
            POSITION (vi$first_no_space);
        ENDIF;

        IF (MARK (NONE) <> END_OF (CURRENT_BUFFER)) THEN
            MOVE_VERTICAL (1);
            vi$new_endpos := MARK (NONE);
            MOVE_VERTICAL (-1);
        ENDIF;

        RETURN (vi$retpos (pos));
    ENDIF;

    POSITION (pos);
    RETURN (0);
ENDPROCEDURE;

!
!   Maintain the repeat count in vi$active_count.  If VI$ACTIVE_COUNT is ZERO,
!   and '0' is typed, this means move to beginning of the line.
!
PROCEDURE vi$repeat_count

    IF VI$ACTIVE_COUNT = 0 THEN
        vi$active_count := INT (ASCII (KEY_NAME (vi$last_key)));
        IF vi$active_count = 0 THEN
            vi$position (vi$fol, 0);
        ENDIF;
    ELSE
        vi$active_count := vi$active_count * 10 +
                                    INT (ASCII (KEY_NAME (vi$last_key)));
    ENDIF;

ENDPROCEDURE;

!
!   The function mapped to <CR>.
!
PROCEDURE vi$_next_line
    POSITION (vi$beg_next);
ENDPROCEDURE;

!
!   Move the cursor to the beginning of the next line
!
PROCEDURE vi$beg_next
    LOCAL
        pos;

    ON_ERROR
        RETURN (MARK (NONE));
    ENDON_ERROR;

    pos := MARK (NONE);
    MOVE_VERTICAL (vi$cur_active_count);
    MOVE_HORIZONTAL (-CURRENT_OFFSET);
    POSITION (vi$first_no_space);
    vi$yank_mode := VI$LINE_MODE;
    vi$new_offset := 1;
    RETURN (vi$retpos (pos));

ENDPROCEDURE;

!
!   This function moves to the first non-blank character of a line
!
PROCEDURE vi$first_no_space

    LOCAL
        pos,
        t_range;

    ON_ERROR
        ! Ignore string not found messages.
    ENDON_ERROR;

    pos := MARK (NONE);
    MOVE_HORIZONTAL (- CURRENT_OFFSET);

    IF (LENGTH (CURRENT_LINE) > 0) THEN
        IF t_range = 0 THEN
            t_range :=
                SEARCH (ANCHOR & SPAN (vi$no_space) &
                                        NOTANY(vi$no_space), FORWARD);
        ENDIF;

        IF t_range <> 0 THEN
            POSITION (END_OF (t_range));
        ELSE
            ! If that fails, then search for a blank line with extra white
            ! space, and move to the end of the white space.

            t_range := SEARCH (ANCHOR & SPAN (vi$no_space), FORWARD);

            IF t_range <> 0 THEN
                POSITION (END_OF (t_range));
            ENDIF;
        ENDIF;
    ENDIF;

    vi$yank_mode := VI$IN_LINE_MODE;
    RETURN (vi$retpos (pos));
ENDPROCEDURE;

!
!   Move by a section in the indicated direction
!
PROCEDURE vi$_section (dir)
    LOCAL
        ch;

    ch := vi$read_a_key;
    IF ((ASCII(ch) = "]") AND (dir = 1)) OR
        ((ASCII(ch) = "[") AND (dir = -1)) THEN
        vi$position (vi$section (dir), 1);
    ELSE
        vi$beep;
    ENDIF;
ENDPROCEDURE;

!
!   Sound a bell.
!
PROCEDURE vi$beep
    LOCAL
        pos;

    IF (vi$error_bells = 0) THEN
        RETURN;
    ENDIF;

    pos := CURRENT_WINDOW;
    SET (BELL, ALL, ON);
    MESSAGE ("");
    SET (BELL, ALL, OFF);
    SET (BELL, BROADCAST, ON);
    POSITION (pos);
ENDPROCEDURE;

!
!   Mapped to '}' and '{', moves by a paragraph in the indicated direction.
!
PROCEDURE vi$_paragraph(dir)
    vi$position (vi$paragraph(dir), 1);
ENDPROCEDURE;

!
!   Mapped to ( moves backward a sentence
!
PROCEDURE vi$_begin_sentence
    vi$position (vi$begin_sentence, 1);
ENDPROCEDURE;

!
!   Mapped to ) moves forward a sentence
!
PROCEDURE vi$_end_sentence
    vi$position (vi$end_sentence, 1);
ENDPROCEDURE;

!
!   Move backward a sentence.
!
PROCEDURE vi$begin_sentence
    LOCAL
        rng,
        spos,
        pos;

    ON_ERROR;
    ENDON_ERROR;

    pos := MARK (NONE);

    MOVE_HORIZONTAL (-1);

    LOOP;
        rng := SEARCH (
            (("" | " " | "  ") & ANY (vi$_upper_chars)),
            REVERSE, EXACT);

        EXITIF rng = 0;

        POSITION (BEGINNING_OF (rng));
        IF INDEX ("     ", CURRENT_CHARACTER) = 0 THEN
            MOVE_HORIZONTAL (-1);
        ENDIF;
        IF INDEX ("     ", CURRENT_CHARACTER) <> 0 THEN
            IF (CURRENT_CHARACTER = " ") THEN
                MOVE_HORIZONTAL (-1);
                IF INDEX ("     ", CURRENT_CHARACTER) <> 0 THEN
                    MOVE_HORIZONTAL (-1);
                    IF INDEX ("?.!", CURRENT_CHARACTER) <> 0 THEN
                        MOVE_HORIZONTAL (3);
                        RETURN (vi$retpos (pos));
                    ENDIF;
                ENDIF;
            ELSE
                MOVE_HORIZONTAL (1);
                RETURN (vi$retpos (pos));
            ENDIF;
        ENDIF;
        POSITION (BEGINNING_OF (rng));
        MOVE_HORIZONTAL (-1);
    ENDLOOP;

    RETURN (0);
ENDPROCEDURE;

!
!   Move to next paragraph
!
PROCEDURE vi$paragraph (dir)
    RETURN (vi$para_sect (dir, vi$para_pat));
ENDPROCEDURE;

!
!   Find next paragraph or section.
!
PROCEDURE vi$para_sect (dir, pat)
    LOCAL
        loc,
        direct,
        pos;

    pos := MARK (NONE);

    IF (dir < 0) THEN
        direct := REVERSE;
        MOVE_VERTICAL (-1);
    ELSE
        direct := FORWARD;
        MOVE_VERTICAL (1);
    ENDIF;

    SET (TIMER, ON, "Searching...");
    loc := SEARCH (pat, direct, NO_EXACT);
    SET (TIMER, OFF);

    IF (loc <> 0) THEN
        RETURN (BEGINNING_OF (loc));
    ELSE
        SET (TIMER, ON, "Searching...");
        loc := SEARCH (vi$next_blank, direct, NO_EXACT);
        SET (TIMER, OFF);
        IF (loc <> 0) THEN
            RETURN (BEGINNING_OF (loc));
        ENDIF;
    ENDIF;

    POSITION (pos);
    RETURN (0);
ENDPROCEDURE;

!
!   Move to next section
!
PROCEDURE vi$section (dir)
    RETURN (vi$para_sect (dir, vi$sect_pat));
ENDPROCEDURE;

!
!   Move forward a sentence.
!
PROCEDURE vi$end_sentence
    LOCAL
        rng,
        spos,
        pos;

    ON_ERROR;
    ENDON_ERROR;

    pos := MARK (NONE);

    MOVE_HORIZONTAL (1);

    LOOP;
        rng := SEARCH (ANY (vi$_upper_chars), FORWARD, EXACT);

        EXITIF rng = 0;

        POSITION (BEGINNING_OF (rng));
        IF INDEX ("     ", CURRENT_CHARACTER) = 0 THEN
            MOVE_HORIZONTAL (-1);
        ENDIF;
        IF INDEX ("     ", CURRENT_CHARACTER) <> 0 THEN
            IF (CURRENT_CHARACTER = " ") THEN
                MOVE_HORIZONTAL (-1);
                IF INDEX ("     ", CURRENT_CHARACTER) <> 0 THEN
                    MOVE_HORIZONTAL (-1);
                    IF INDEX ("?.!", CURRENT_CHARACTER) <> 0 THEN
                        MOVE_HORIZONTAL (3);
                        RETURN (vi$retpos (pos));
                    ENDIF;
                ENDIF;
            ELSE
                MOVE_HORIZONTAL (1);
                RETURN (vi$retpos (pos));
            ENDIF;
        ENDIF;
        POSITION (BEGINNING_OF (rng));
        MOVE_HORIZONTAL (1);
    ENDLOOP;

    RETURN (0);
ENDPROCEDURE;

!
!   This function returns the value in vi$active count.  It takes into
!   account that when vi$active_count is zero, it should really be
!   one.
!
PROCEDURE vi$cur_active_count
    LOCAL
        resp,
        old_cnt;

    old_cnt := vi$active_count;
    vi$active_count := 0;
    IF old_cnt <= 0 THEN
        old_cnt := 1;
    ENDIF;

    RETURN (old_cnt);
ENDPROCEDURE;

!
!   The function mapped to 'p'.
!
PROCEDURE vi$put_after (dest_buf)

    LOCAL
        source,
        pos;

    source := vi$cur_text;

    IF (GET_INFO (dest_buf, "TYPE") = BUFFER) THEN
        source := dest_buf;
    ENDIF;

    IF (GET_INFO (source, "TYPE") = BUFFER) THEN
        pos := MARK (NONE);
        POSITION (BEGINNING_OF (source));
        vi$yank_mode := INT (vi$current_line);
        POSITION (pos);
    ENDIF;

    IF (source = "") THEN
        RETURN;
    ENDIF;

    IF (vi$yank_mode = VI$LINE_MODE) THEN
        IF (MARK(NONE) <> END_OF (CURRENT_BUFFER)) THEN
            MOVE_VERTICAL (1);
        ENDIF;
    ELSE
        IF (LENGTH (CURRENT_LINE) > 0) THEN
            MOVE_HORIZONTAL (1);
        ENDIF;
    ENDIF;

    vi$put_here (VI$AFTER, source);
ENDPROCEDURE;

!
!   The function mapped to 'P'.
!
PROCEDURE vi$put_here (here_or_below, dest_buf)
    LOCAL
        olen,
        source,
        pos;

    source := vi$cur_text;
    olen := GET_INFO (CURRENT_BUFFER, "RECORD_COUNT");

    IF (GET_INFO (dest_buf, "TYPE") = BUFFER) THEN
        source := dest_buf;
    ENDIF;

    IF (GET_INFO (source, "TYPE") = BUFFER) THEN
        pos := MARK (NONE);
        POSITION (BEGINNING_OF (source));
        IF (MARK (NONE) = END_OF (source)) THEN
            RETURN;
        ENDIF;
        vi$yank_mode := INT (vi$current_line);
        ERASE_LINE;
        POSITION (pos);
    ELSE
        IF (source = "") THEN
            RETURN;
        ENDIF;
    ENDIF;

    IF source = 0 THEN
        vi$message ("Bad buffer for put!");
        RETURN;
    ENDIF;

    IF (vi$yank_mode = VI$LINE_MODE) THEN
        MOVE_HORIZONTAL (-CURRENT_OFFSET);
    ENDIF;

    pos := vi$get_undo_start;

    COPY_TEXT (source);
    APPEND_LINE;
    MOVE_HORIZONTAL (-1);
    vi$undo_end := MARK (NONE);
    MOVE_HORIZONTAL (1);

    vi$kill_undo;

    IF (here_or_below = VI$AFTER) AND (vi$yank_mode = VI$LINE_MODE) THEN
        MOVE_HORIZONTAL (-CURRENT_OFFSET);
    ENDIF;

    vi$undo_start := vi$set_undo_start (pos);

    ! Put the mode back into the buffer.

    IF (GET_INFO (source, "TYPE") = BUFFER) THEN
        POSITION (BEGINNING_OF (source));
        COPY_TEXT (STR (vi$yank_mode));
        SPLIT_LINE;
        POSITION (vi$undo_start);
    ENDIF;

    IF (here_or_below = VI$AFTER) AND (vi$yank_mode = VI$IN_LINE_MODE) THEN
        POSITION (vi$undo_end);
    ENDIF;

    vi$check_length (olen);
ENDPROCEDURE;

!
!   Function mapped to 'o'.  Note that this makes undo NOT place
!   the cursor where is really should be.
!
PROCEDURE vi$open_below
    LOCAL
        uline;

    ON_ERROR
        ! Ignore attempt to move past EOB errors
    ENDON_ERROR;

    uline := vi$cur_line_no;
    MOVE_VERTICAL (1);
    vi$open_here;
    vi$undo_line := uline;

ENDPROCEDURE;

!
!   Function mapped to 'O'
!
PROCEDURE vi$open_here

    LOCAL
        uline,
        offs,
        cnt,
        epos,
        spos;

    uline := vi$cur_line_no;
    offs := CURRENT_OFFSET;

    MOVE_HORIZONTAL (- CURRENT_OFFSET);

    IF (MARK (NONE) <> BEGINNING_OF (CURRENT_BUFFER)) THEN
        MOVE_HORIZONTAL (-1);
        spos := MARK (NONE);
        MOVE_HORIZONTAL (1);
    ELSE
        spos := 0;
    ENDIF;

    SPLIT_LINE;

    MOVE_VERTICAL (-1);

    cnt := vi$while_not_esc;
    epos := MARK (NONE);

    IF (cnt <> 0) THEN
        IF (LENGTH (vi$current_line) > 0) THEN
            MOVE_HORIZONTAL (1);
        ENDIF;
    ENDIF;

    vi$undo_end := MARK (NONE);

    IF spos <> 0 THEN
        POSITION (spos);
        MOVE_HORIZONTAL (1);
    ELSE
        POSITION (BEGINNING_OF (CURRENT_BUFFER));
    ENDIF;

    vi$undo_start := MARK (NONE);
    POSITION (epos);

    vi$kill_undo;

    vi$undo_line := uline;
    vi$undo_offset := offs;
ENDPROCEDURE;

!
!   This function guards the right margin, and the end of the buffer so
!   that the cursor never is displayed past those boundries.
!
PROCEDURE vi$check_rmarg

    ON_ERROR;
        ! ignore "Can't return line and end of buffer" messages
        RETURN;
    ENDON_ERROR;

    IF (LENGTH (vi$current_line) > 0) THEN
        IF (CURRENT_OFFSET = LENGTH (vi$current_line)) THEN
            MOVE_HORIZONTAL (-1);
        ENDIF;
    ENDIF;

    IF (MARK (NONE) = END_OF (CURRENT_BUFFER)) THEN
        MOVE_VERTICAL (-1);
    ENDIF;
ENDPROCEDURE;

!
!   The function mapped to 'h'.
!
PROCEDURE vi$move_left
    vi$position (vi$left, 0);
ENDPROCEDURE;

!
!   The function mapped to 'l'.
!
PROCEDURE vi$move_right
    vi$position (vi$right, 0);
ENDPROCEDURE;

!
!   The function mapped to 'j'
!
PROCEDURE vi$move_down
    LOCAL
        save_mark;

    save_mark := 0;

    IF (vi$active_count >= vi$report) THEN
        save_mark := 1;
    ENDIF;

    vi$position (vi$downline (0), save_mark);
ENDPROCEDURE;

!
!   The function mapped to 'k'.
!
PROCEDURE vi$move_up
    LOCAL
        save_mark;

    save_mark := 0;

    IF (vi$active_count >= vi$report) THEN
        save_mark := 1;
    ENDIF;

    vi$position (vi$upline, save_mark);
ENDPROCEDURE;

!
!   The function mapped to 'i'.
!
PROCEDURE vi$insert_here
    LOCAL
        act_cnt,
        rnge,
        ccnt,
        epos,
        spos;

    vi$kill_undo;

    IF (MARK (NONE) <> BEGINNING_OF (CURRENT_BUFFER)) THEN
        MOVE_HORIZONTAL (-1);
        spos := MARK (NONE);
        MOVE_HORIZONTAL (1);
    ELSE
        spos := 0;
    ENDIF;

    vi$undo_start := MARK (NONE);

    ccnt := vi$while_not_esc;

    vi$undo_end := 0;

    IF (ccnt > 0) THEN
        IF (CURRENT_OFFSET = 0) AND
                        ((ccnt > 1) OR (LENGTH (CURRENT_LINE) = 0)) AND
                        (MARK (NONE) <> BEGINNING_OF (CURRENT_BUFFER)) THEN
            MOVE_HORIZONTAL (-1);
            epos := MARK (NONE);
            MOVE_HORIZONTAL (1);
        ELSE
            epos := MARK (NONE);
        ENDIF;
    ELSE
        epos := 0;
    ENDIF;

    IF epos <> 0 THEN
        act_cnt := vi$cur_active_count;

        IF spos <> 0 THEN
            POSITION (spos);
            MOVE_HORIZONTAL (1);
        ELSE
            POSITION (BEGINNING_OF (CURRENT_BUFFER));
        ENDIF;
        vi$undo_start := MARK (NONE);

        POSITION (epos);

        IF (vi$undo_start = 0) OR (epos = 0) THEN
            vi$message ("Ooops, bad markers in vi$insert_here");
            RETURN ;
        ENDIF;

        rnge := CREATE_RANGE (vi$undo_start, epos, NONE);

        LOOP
            EXITIF act_cnt < 2;
            MOVE_HORIZONTAL (1);

            IF rnge = 0 THEN
                vi$message ("Ooops, generated a bad range in vi$insert_here");
                RETURN ;
            ENDIF;

            COPY_TEXT (rnge);
            act_cnt := act_cnt - 1;
            MOVE_HORIZONTAL (-1);
        ENDLOOP;

        vi$undo_end := MARK (NONE);
        IF (CURRENT_OFFSET = LENGTH (vi$current_line)) THEN
            MOVE_HORIZONTAL (1);
        ENDIF;
    ENDIF;
ENDPROCEDURE;

!
!   The function mapped to 'I'
!
PROCEDURE vi$insert_at_begin

    MOVE_HORIZONTAL (- CURRENT_OFFSET);
    vi$_bol;
    vi$insert_here;

ENDPROCEDURE;

!
!   The function mapped to 'a'
!
PROCEDURE vi$insert_after

    IF (LENGTH (vi$current_line) > 0) THEN
        IF (CURRENT_OFFSET < LENGTH(vi$current_line)) THEN
            MOVE_HORIZONTAL (1);
        ENDIF;
    ENDIF;
    vi$insert_here;

ENDPROCEDURE;

!
!  A do nothing function
!
PROCEDURE vi$_dummy
ENDPROCEDURE;

!
!  Do the command line input processing
!
PROCEDURE vi$while_not_esc

    LOCAL
        max_mark,
        start_pos,
        max_col;

    max_col := CURRENT_OFFSET;
    start_pos := max_col;
    max_mark := MARK(NONE);
    vi$update (CURRENT_WINDOW);

    RETURN (vi$line_edit (max_col, start_pos, max_mark, 0));
ENDPROCEDURE;

!
!   Insert text into the buffer using standard VI insertion.
!   Used by CHANGE, APPEND, INSERT, and REPLACE functions.
!
PROCEDURE vi$line_edit (max_col, start_pos, max_mark, replace)

    LOCAL
        chcnt,
        offset,
        seen_eol,
        col,
        cnt,
        tabstops,
        current_mark,
        desc,
        start_ins,
        ins_text,
        should_wrap,
        abbrs,
        rchar,
        abbrlen,
        cabbr,
        pos,
        in_char;

    ON_ERROR
    ENDON_ERROR;

    IF (vi$show_mode) THEN
        vi$mess_select (BOLD);
        MESSAGE (FAO ("!7*  INSERT"));
        vi$mess_select (REVERSE);
    ENDIF;
    chcnt := 0;
    seen_eol := 0;

    abbrs := EXPAND_NAME ("vi$abbr_", VARIABLES) + " ";

    cabbr := "";
    abbrlen := 0;

    SET (INSERT, CURRENT_BUFFER);
    IF (max_col > CURRENT_OFFSET) OR (replace <> 0) THEN
        SET (OVERSTRIKE, CURRENT_BUFFER);
    ENDIF;

    start_ins := MARK (NONE);

    LOOP
        LOOP
            in_char := vi$read_a_key;
            desc := LOOKUP_KEY (KEY_NAME (in_char), COMMENT, vi$edit_keys);
            EXITIF (desc <> "reinsert");

            IF max_mark <> MARK (NONE) THEN
                current_mark := MARK (NONE);
                POSITION (max_mark);
                MOVE_HORIZONTAL (-1);

                ERASE (CREATE_RANGE (MARK (NONE), current_mark, NONE));
            ENDIF;

            SET (INSERT, CURRENT_BUFFER);
            COPY_TEXT (vi$last_insert);
            APPEND_LINE;

            max_col := CURRENT_OFFSET;
            start_pos := CURRENT_OFFSET;
            max_mark := MARK(NONE);
            chcnt := chcnt + 1;
        ENDLOOP;

        IF (desc = "active_macro") THEN
            EXECUTE (LOOKUP_KEY (KEY_NAME (in_char), PROGRAM, vi$edit_keys));
        ELSE
            EXITIF desc = "escape";

            should_wrap := (vi$wrap_margin <> 0) AND
                            ((CURRENT_OFFSET + vi$wrap_margin) > vi$scr_width);

            IF (desc <> "eol") AND (desc <> "bword") AND (desc <> "bs") THEN
                IF (should_wrap) THEN

                    offset := 0;
                    MOVE_HORIZONTAL (-1);

                    LOOP
                        EXITIF (CURRENT_OFFSET = 0);
                        EXITIF (INDEX ("    ", CURRENT_CHARACTER) <> 0);
                        MOVE_HORIZONTAL (-1);
                        offset := offset + 1;
                    ENDLOOP;

                    IF (offset <> 0) THEN
                        ERASE_CHARACTER (1);
                        LOOP
                            EXITIF (CURRENT_OFFSET = 0);
                            MOVE_HORIZONTAL (-1);
                            EXITIF (
                                INDEX ("    ", CURRENT_CHARACTER) = 0);
                            ERASE_CHARACTER (1);
                        ENDLOOP;
                    ENDIF;

                    IF (CURRENT_OFFSET <> 0) THEN
                        MOVE_HORIZONTAL (1);
                        SPLIT_LINE;
                        max_col := CURRENT_OFFSET;
                        start_pos := CURRENT_OFFSET;
                        max_mark := MARK(NONE);
                        MOVE_HORIZONTAL (offset);
                    ELSE
                        MOVE_HORIZONTAL (offset);
                        SPLIT_LINE;
                        max_col := CURRENT_OFFSET;
                        start_pos := CURRENT_OFFSET;
                        max_mark := MARK(NONE);
                    ENDIF;
                ENDIF;

                vi$update (CURRENT_WINDOW);

                IF desc = "vquote" THEN
                    in_char := vi$read_a_key;
                ENDIF;

                IF in_char = TAB_KEY THEN
                    vi$abbr (abbrs, 0, cabbr, abbrlen);
                    IF (vi$use_tabs = 1) THEN
                        COPY_TEXT (ASCII (9));
                    ELSE
                        cnt := 0;
                        col := GET_INFO (SCREEN, "CURRENT_COLUMN");
                        tabstops := GET_INFO (CURRENT_BUFFER, "TAB_STOPS");

                        IF (GET_INFO (tabstops, "TYPE") <> STRING) THEN
                            LOOP
                                EXITIF (col - ((col / tabstops) *
                                                                tabstops) = 0);
                                cnt := cnt + 1;
                                col := col + 1;
                            ENDLOOP;

                            chcnt := chcnt + cnt;
                            LOOP
                                EXITIF (cnt < 0);
                                IF (CURRENT_OFFSET = max_col) AND
                                                ((replace = 0) OR seen_eol) THE
N
                                    SET (INSERT, CURRENT_BUFFER);
                                ELSE
                                    IF CURRENT_OFFSET > max_col THEN
                                        max_col := CURRENT_OFFSET;
                                        max_mark := MARK (NONE);;
                                    ENDIF;
                                ENDIF;
                                COPY_TEXT (" ");
                                cnt := cnt - 1;
                            ENDLOOP
                        ELSE

                            ! Give up on windows with weird tab stops.

                            COPY_TEXT (ASCII (9));
                        ENDIF;
                    ENDIF;
                    chcnt := chcnt + 1;
                ELSE
                    IF (in_char <= CTRL_Z_KEY) AND (in_char >= CTRL_A_KEY) THEN
                        in_char := (in_char - CTRL_A_KEY) /
                                    (CTRL_B_KEY - CTRL_A_KEY) + 1;
                    ENDIF;

                    rchar := vi$ascii(in_char);

                    IF (INDEX (vi$_ws, rchar) <> 0) THEN
                        chcnt := chcnt + vi$abbr (abbrs, rchar, cabbr, abbrlen)
;
                    ELSE
                        COPY_TEXT (rchar);
                        IF (INDEX(vi$_upper_chars, rchar) <> 0) THEN
                            cabbr := cabbr + "_";
                        ENDIF;
                        cabbr := cabbr + rchar;
                        abbrlen := abbrlen + 1;
                        chcnt := chcnt + 1;
                    ENDIF;
                ENDIF;

                IF (CURRENT_OFFSET = max_col) AND
                                    ((replace = 0) OR seen_eol) THEN
                    SET (INSERT, CURRENT_BUFFER);
                ELSE
                    IF CURRENT_OFFSET > max_col THEN
                        max_col := CURRENT_OFFSET;
                        max_mark := MARK (NONE);
                    ENDIF;
                ENDIF;
            ELSE
                IF desc = "bs" THEN
                    IF start_pos < CURRENT_OFFSET THEN
                        ! Delete backspace and the character before it.
                        vi$del_a_key;
                        vi$del_a_key;
                        SET (OVERSTRIKE, CURRENT_BUFFER);
                        MOVE_HORIZONTAL (-1);
                        chcnt := chcnt - 1;
                    ENDIF;
                ELSE
                    IF desc = "eol" THEN
                        IF (max_mark <> MARK (NONE)) AND (replace = 0) THEN
                            current_mark := MARK (NONE);
                            POSITION (max_mark);
                            MOVE_HORIZONTAL (-1);
                            ERASE (CREATE_RANGE (MARK (NONE),
                                                        current_mark, NONE));
                        ENDIF;
                        vi$abbr (abbrs, 0, cabbr, abbrlen);
                        SPLIT_LINE;
                        chcnt := chcnt + 1;
                        seen_eol := 1;
                        IF (CURRENT_BUFFER = vi$dcl_buf) AND (vi$send_dcl) THEN
                            MOVE_VERTICAL (-1);
                            vi$send_to_dcl (CURRENT_LINE);
                            MOVE_VERTICAL (1);
                        ENDIF;
                        max_col := CURRENT_OFFSET;
                        start_pos := CURRENT_OFFSET;
                        SET (INSERT, CURRENT_BUFFER);
                        max_mark := MARK(NONE);
                        IF (CURRENT_BUFFER = vi$dcl_buf) AND (vi$send_dcl) THEN
                            EXITIF (1);
                        ENDIF;
                    ELSE
                        IF (desc = "bword") THEN

                            ! Backup over whitespace.

                            LOOP
                                EXITIF start_pos = CURRENT_OFFSET;
                                MOVE_HORIZONTAL (-1);
                                chcnt := chcnt - 1;
                                EXITIF (INDEX ("    ", CURRENT_CHARACTER) = 0);
                                SET (OVERSTRIKE, CURRENT_BUFFER);
                            ENDLOOP;

                            ! Backup over noblank words.

                            LOOP
                                EXITIF start_pos = CURRENT_OFFSET;
                                SET (OVERSTRIKE, CURRENT_BUFFER);
                                IF (INDEX ("    ", CURRENT_CHARACTER) <> 0) THE
N
                                    chcnt := chcnt + 1;
                                    MOVE_HORIZONTAL (1);
                                    EXITIF (1);
                                ENDIF;
                                MOVE_HORIZONTAL (-1);
                                chcnt := chcnt - 1;
                            ENDLOOP;
                        ENDIF;
                    ENDIF;
                ENDIF;
            ENDIF;
        ENDIF;

        vi$update (CURRENT_WINDOW);
    ENDLOOP;

    IF max_mark <> MARK (NONE) THEN
        current_mark := MARK (NONE);
        IF (NOT seen_eol) AND (replace <> 0) THEN
            SET (OVERSTRIKE, CURRENT_BUFFER);
            COPY_TEXT (SUBSTR (replace, CURRENT_OFFSET + 1,
                                                max_col - CURRENT_OFFSET));
            POSITION (current_mark);
        ELSE
            POSITION (max_mark);
            IF (MARK(NONE) <> BEGINNING_OF (CURRENT_BUFFER)) THEN
                MOVE_HORIZONTAL (-1);
            ENDIF;
            ERASE (CREATE_RANGE (MARK (NONE), current_mark, NONE));
        ENDIF;
    ENDIF;

    IF (CURRENT_OFFSET > 0) AND
                            (MARK(NONE) <> BEGINNING_OF (CURRENT_BUFFER)) THEN
        MOVE_HORIZONTAL (-1);
    ENDIF;

    ins_text := CREATE_RANGE (start_ins, MARK (NONE), NONE);

    ! Save last inserted text to buffer.

    ERASE (vi$last_insert);
    pos := MARK (NONE);

    POSITION (vi$last_insert);
    COPY_TEXT (ins_text);
    SPLIT_LINE;
    POSITION (BEGINNING_OF (vi$last_insert));

    POSITION (pos);

    SET (INSERT, CURRENT_BUFFER);

    IF (vi$show_mode) THEN
        MESSAGE ("");
    ENDIF;
    RETURN (chcnt);
ENDPROCEDURE;

!
!   Check to see if 'cabbr' is a known abbreviation, and substitute the
!   proper text if it is.
!
PROCEDURE vi$abbr (abbrs, rchar, cabbr, abbrlen)
    LOCAL
        strg;

    strg := "";

    IF (abbrlen > 0) THEN
        EDIT (cabbr, UPPER);
        IF (INDEX (abbrs, "VI$ABBR_"+cabbr+" ") <> 0) THEN
            vi$global_var := 0;
            EXECUTE (COMPILE ("vi$global_var := vi$abbr_"+cabbr+";"));
            IF (vi$global_var <> 0) THEN
                ERASE_CHARACTER (-abbrlen);
                strg := vi$global_var;
                COPY_TEXT (strg);
            ENDIF;
        ENDIF;
        cabbr := "";
        abbrlen := 0;
    ENDIF;
    IF (rchar <> 0) THEN
        COPY_TEXT (rchar);
    ENDIF;
    RETURN (LENGTH (strg) + (rchar <> 0));
ENDPROCEDURE;

!
!   Return a string describing the KEY_NAME passed.  For control characters,
!   it is "^?" where the '?' is A-Z.  Otherwise, the value returned by the
!   ASCII() builtin is used.
!
PROCEDURE vi$ascii_name (key_n)
    LOCAL
        key;

    key := (key_n - CTRL_A_KEY) / (CTRL_B_KEY - CTRL_A_KEY);
    IF (key > 31) OR (key < 0) THEN
        key := ASCII (key_n);
    ELSE
        key := "^" + ASCII(key+65);
    ENDIF;

    RETURN (key);
ENDPROCEDURE;

!
!   Perform some mapping of keys to different ASCII values.
!
PROCEDURE vi$ascii (key_n)
    IF key_n = F12 THEN
        RETURN (ASCII (8));
    ENDIF;
    IF key_n = F11 THEN
        RETURN (ASCII (27));
    ENDIF;
    IF key_n = PF1 THEN
        RETURN (ASCII (27));
    ENDIF;
    IF key_n = RET_KEY THEN
        RETURN (ASCII (13));
    ENDIF;
    IF key_n = TAB_KEY THEN
        RETURN (ASCII (9));
    ENDIF;
    RETURN (ASCII (key_n));
ENDPROCEDURE;

!
!   Move up by screens
!
PROCEDURE vi$prev_screen
    ON_ERROR
    ENDON_ERROR;

    MOVE_VERTICAL (-vi$cur_active_count *
                        GET_INFO (CURRENT_WINDOW, "VISIBLE_LENGTH"));

    vi$position (vi$first_no_space, 0);
ENDPROCEDURE;

!
!   Move down by screens
!
PROCEDURE vi$next_screen
    ON_ERROR
    ENDON_ERROR;

    MOVE_VERTICAL (vi$cur_active_count *
                        (GET_INFO (CURRENT_WINDOW, "VISIBLE_LENGTH") + 2));

    vi$position (vi$first_no_space, 0);
ENDPROCEDURE;

!
! Scroll forward one screen
!
PROCEDURE vi$screen_forward

    vi$scroll_screen (1);

ENDPROCEDURE;

!
! Scroll back one screen
!
PROCEDURE vi$screen_backward

    vi$scroll_screen (-1);

ENDPROCEDURE;

!
!   Scroll the screen up or down depending on the sign of "how_many_screens"
!   The magnitude actually has effect as well, but is never greater than 1
!   in this use.
!
PROCEDURE vi$scroll_screen (how_many_screens)

    LOCAL
        scroll_window,          ! Window to be scrolled
        this_window,            ! Current window
        this_column,            ! Current column in scroll_window
        this_row,               ! Current row in scroll_window
        old_scroll_top,         ! Original value of scroll_top
        old_scroll_bottom,      ! Original value of scroll_bottom
        old_scroll_amount;      ! Original value of scroll_amount

    ! Trap and ignore messages about move beyond buffer boundaries -
    ! just move to top or bottom line of buffer

    ON_ERROR
    ENDON_ERROR;

    this_window := CURRENT_WINDOW;

    scroll_window := this_window;

    IF vi$active_count <> 0 THEN
        vi$how_much_scroll := vi$cur_active_count;
    ENDIF;

    this_row := GET_INFO (scroll_window, "CURRENT_ROW");

    IF this_row = 0 THEN
        this_row := GET_INFO (scroll_window, "VISIBLE_TOP");
    ENDIF;

    this_column := GET_INFO (scroll_window, "CURRENT_COLUMN");
    MOVE_HORIZONTAL (-CURRENT_OFFSET);

    old_scroll_top := GET_INFO (scroll_window, "SCROLL_TOP");
    old_scroll_bottom := GET_INFO (scroll_window, "SCROLL_BOTTOM");
    old_scroll_amount := GET_INFO (scroll_window, "SCROLL_AMOUNT");

    SET (SCROLLING, scroll_window, ON,
                    this_row - GET_INFO (scroll_window, "VISIBLE_TOP"),
                    GET_INFO (scroll_window, "VISIBLE_BOTTOM") - this_row, 0);

    MOVE_VERTICAL (how_many_screens * vi$how_much_scroll);
    vi$update (scroll_window);

    IF this_window <> CURRENT_WINDOW THEN
        POSITION (this_window);
    ENDIF;

    SET (SCROLLING, scroll_window, ON, old_scroll_top, old_scroll_bottom,
                                                            old_scroll_amount);
ENDPROCEDURE;

!
!   Move forward logical words
!
PROCEDURE vi$_word_forward
    vi$position (vi$word_move (1), 0);
ENDPROCEDURE;

!
!   Move backward logical words
!
PROCEDURE vi$_word_back
    vi$position (vi$word_move(-1), 0);
ENDPROCEDURE;

!
!   Move by logical word taking into account the repeat count
!
PROCEDURE vi$word_move(dir)
    LOCAL
        old_pos,
        pos;

    old_pos := MARK (NONE);

    IF vi$active_count <= 0 THEN
        vi$active_count := 1;
    ENDIF;

    LOOP
        pos := vi$move_logical_word (dir);
        EXITIF pos = 0;
        POSITION (pos);
        vi$active_count := vi$active_count - 1;
        EXITIF vi$active_count = 0;
    ENDLOOP;

    vi$yank_mode := VI$IN_LINE_MODE;
    RETURN (vi$retpos (old_pos));
ENDPROCEDURE;

!
!   Move to end of logical word
!
PROCEDURE vi$_word_end
    vi$position (vi$word_end, 0);
ENDPROCEDURE;

!
!   Move to end of physical word
!
PROCEDURE vi$_full_word_end
    vi$position (vi$full_word_end, 0);
ENDPROCEDURE;

!
!   Move to the end of the current word.
!
PROCEDURE vi$word_end
    LOCAL
        old_pos,
        pos;

    old_pos := MARK (NONE);

    IF vi$active_count <= 0 THEN
        vi$active_count := 1;
    ENDIF;

    LOOP
        pos := vi$move_logical_end;
        EXITIF pos = 0;
        POSITION (pos);
        vi$active_count := vi$active_count - 1;
        EXITIF vi$active_count = 0;
    ENDLOOP;

    vi$yank_mode := VI$IN_LINE_MODE;
    RETURN (vi$retpos (old_pos));
ENDPROCEDURE;

!
!   Move to the end of a blank (eol is also considered blank) terminated word.
!
PROCEDURE vi$full_word_end

    LOCAL
        old_pos,
        pos;

    old_pos := MARK (NONE);

    IF vi$active_count <= 0 THEN
        vi$active_count := 1;
    ENDIF;

    LOOP
        pos := vi$move_full_end;
        EXITIF pos = 0;
        POSITION (pos);
        vi$active_count := vi$active_count - 1;
        EXITIF vi$active_count = 0;
    ENDLOOP;

    vi$yank_mode := VI$IN_LINE_MODE;
    RETURN (vi$retpos (old_pos));
ENDPROCEDURE;

!
!   Move forward by ONE white-space delimited word
!
PROCEDURE vi$_full_word_forward
    vi$position (vi$full_word_move (1), 0);
ENDPROCEDURE;

!
!
!   Move backward by ONE white-space delimited word
!
PROCEDURE vi$_full_word_back
    vi$position (vi$full_word_move (-1), 0);
ENDPROCEDURE;

!
!   Move by physical word taking the repeat count into account
!
PROCEDURE vi$full_word_move (dir)

    LOCAL
        old_pos,
        pos;

    old_pos := MARK (NONE);

    IF vi$active_count <= 0 THEN
        vi$active_count := 1;
    ENDIF;

    LOOP
        pos := vi$move_full_word (dir);
        EXITIF pos = 0;
        POSITION (pos);
        vi$active_count := vi$active_count - 1;
        EXITIF vi$active_count = 0;
    ENDLOOP;

    vi$yank_mode := VI$IN_LINE_MODE;
    RETURN (vi$retpos (old_pos));
ENDPROCEDURE;

!
!   Move the cursor by BLANK separated words.  DIRECTION is either
!   +1, or -1 to indicate the direction (forward, or backword respectfully)
!   to move
!
PROCEDURE vi$move_full_word (direction)

    LOCAL
        pos;

    pos := MARK (NONE);

$$EOD$