/*  SCREEN_EDIT.PL  */
 
 
:- module screen_edit.
 
 
:- public edit/2,
          read_back/2.
 
 
/*
SPECIFICATION
-------------
 
This module defines a portable screen-editor, to implement the "edit"
command.
 
 
PUBLIC edit( List0+, Result- ):
-------------------------------
 
List0 is a list of characters formed from the text of a set of facts.
One newline character should be included between lines, but newlines
are not necessary at the start or end of the list.
 
Result will become either
    use(List)
if the user exits from the edit, or
    quit
if he quits.
 
The edit keys are, assuming a VT100 keyboard layout:
    Cursor-up (numeric keypad 8)       Up one character.
    Cursor-down (numeric keypad 2)     Down one character.
    Cursor-left (numeric keypad 4)     Left one character.
    Cursor-left (numeric keypad 6)     Right one character.
    Enter-v                            Redraw the screen.
    Enter-x                            Exit, using edited text.
    Enter-q                            Quit, making no changes.
    Return                             Insert a newline.
    Delete                             Delete previous character.
    Any character with
    ASCII value < 32                   Illegal --- ignored.
    Any other character                Inserted this character.
 
The screen-editor will only work on a VT100-compatible terminal.
 
 
PUBLIC read_back( Chars+, Status- ):
------------------------------------
 
Exported to COMMANDS to read back sentences from Chars, assumed to
be the result of edit/2.
 
If there is a tokenisation error in Chars, Status will be
    token_error( Message, Input )
otherwise
    ok
.
*/
 
 
/*
IMPLEMENTATION
--------------
 
This may well be the only screen editor written (almost) entirely in
Prolog; but for portability, I couldn't do anything else.
 
 
How text is represented.
------------------------
 
Because Prolog doesn't have random-access character arrays, I had to
find some reasonably fast way of representing lines. The solution, which
I have since discovered is used by others ("The Art of Prolog") is to
represent a line as a pair of lists, where the first represents all the
characters to the left of the cursor. The first element of the second
represents the character under the cursor; the succeeding elements,
those to its right.
 
Thus the line
    abCdef
would be represented by the lists
    [ b, a ] [ C, d, e, f ]
if C is the character under the cursor.
 
Because the first list is in reverse order, many operations can be done
quite simply. For example, moving the cursor one place right:
    abcDef
involves transferring one character
    [ c, b, a ] [ D, e, f ]
 
Similarly, inserting a character
    abCdef
    abxCdef
involves prepending a character to the left-hand list:
    [ x, b, a ] [ C, d, e, f ]
 
 
In the code, we represent these as triples
    line( Left, Right, Xc )
where Xc is the position of the cursor, relative to start=1. Right
always contains at least one character, which may be a space. This
simplifies some of the line-update predicates.
 
 
The same representation is used at the next level up for blocks of
lines. The entire edit text is represented as
    block( Top, Bottom )
where Top and Bottom are each lists of line structures. Top is in
reverse order; the first line of Bottom is the one containing the
cursor. Bottom always contains at least one line.
 
 
The main problem with this representation is that when the user moves
the cursor up or down from L1 to L2 it's necessary to move the boundary
between left- and right-lists in the line L2 he's just moved to. This is
necessary so the cursor can be displayed, but is an unwanted overhead if
he's then going to move onto another line. I experimented with
representations that delay moving the boundary, but this tends to
complicate other parts of the editor. However, maybe it can be done more
neatly...
 
 
In fact, edit text is stored with some other information, in a
structure
    buffer( Block, Xw, Yw, Xc, Yc )
where Xc and Yc are the cursor position, relative again to (1,1).
It is a program invariant that Xc and Yc both >= 1.
 
This editor can handle scrolling, and can edit texts too big to fit
onto the screen. We think in terms of a screen-sized edit window
which fits over the text. Its top left-hand corner is at text coordinates
(Xw,Yw). It is a program invariant that Xw and Yw both >= 1; you can't
scroll off the top of a file, or off the left margin.
 
Most of the editor needs to know nothing about screen coordinates: the
screen location (Xs,Ys) of the character at (Xc,Yc) is generated by
subtracting (Xw,Yw) and adding an offset to the Y value to allow for the
edit header above the text.
 
The screen (i.e. window) is (obviously) assumed to have positive width
and height. It is another program invariant that the cursor is always
visible within the window; if necessary, the window must be scrolled to
maintain this. Knowing that the cursor was visible just before scrolling
simplifies the calculation of the scrolling direction.
 
 
The edit cycle.
---------------
 
This is as follows:
    Read a key.
    Update the buffer.
    Generate a screen-update action.
    Use the latter to update the screen image.
 
To save time, the editor does not redraw the entire buffer on each
cycle. Instead, it assumes that the screen holds a correct image of the
previous buffer, and calculates the minimum change necessary to update
this. The changes are represented as "action" structures, which are
comparatively independent of terminal type. To perform them, we call
    update_screen( Action, NewBuffer )
. This predicate can implement the Actions in a number of different ways,
depending on the terminal's capabilities.
 
It is a postcondition of update_screen that the screen image formed 
by drawing NewBuffer from scratch must be the same as that formed by
applying Action to the existing image. Hence, if you want to save
implementation time, you can always just ignore Action and draw out
NewBuffer.
 
 
Reading keys is described at the end of this file. Briefly, the editor
has to be able to read keys such as cursor-down and convert the
resulting character sequence into a structure representing the key. This
is done by
    get_edit_char(C)
which sets C as follows:
    left            Cursor-left
    right           Cursor-right
    up              Cursor-up
    down            Cursor-down
    newline         Newline (Insert a newline)
    delete          Delete
    exit            Enter-X (Exit, saving changes)
    quit            Enter-Q (Quit, ignoring changes)
    refresh         Enter-V (Redraw screen)
    other(C)        A space or printable character, to be inserted
                    verbatim - C is the internal code
    illegal         Anything else - ignored
 
 
edit/2 calls editB to act on each key. It switches on the key structure,
and calls an appropriate predicate such as leftB or downB. These
predicates in turn call (e.g.) left_buf/down_buf and left_scr/down_scr.
The _buf predicates generate a new block and cursor position, but do not
worry about the screen layout or window coordinates. They typically have
the form
    left_buf( Block+, Xc+, Yc+, Ch-, Block_-, Xc_-, Yc_-, Ch_- )
where the first three arguments are the old block and cursor position.
 
The other arguments are instantiated to the new block and cursor
position, and the characters under the old and new cursor. These may be
useful when redrawing the cursor: you might want, e.g. to position the
characters and highlight them. I in fact ignore the Ch and Ch_
arguments, since my terminals have primitives for moving the cursor and
representing it as a flashing block directly.
 
Some _scr predicates may return extra information.
 
The _buf predicates do not worry about the screen image or window
coordinates. These are left to the _scr predicates. They typically look
like
    left_scr( Block_+,             - new block
              Xc+, Yc+, Ch+,       - old cursor and character
              Xc_+, Yc_+, Ch_+,    - new cursor and character
              Xw+, Yw+,            - old window coordinates
              Xw_-, Yw_-,          - new window coordinates
              Actions-             - screen-update action(s).
            )
 
 
Updating the screen.
--------------------
 
This is accomplished by do_screen_actions(A), called from update_screen.
A can be either a single action or a list thereof. Actions are
structures with arguments representing character positions or text to be
redrawn. For example,
    delete_charACT( Xw, Yw, Xc, Yc, R )
deletes the character at text coordinate (Xc,Yc) by drawing the list of
characters R starting from that point. It then moves the cursor to
(Xc,Yc).
 
To perform an action, do_screen_actions calls it as a goal.
 
The implementation of these actions depends on the terminal. For example,
if your terminal can left-scroll part of a line, then you can ignore the
R argument in delete_charACT, and just left-scroll from (Xc,Yc). If not,
you will have to move the cursor to that point, and then overwrite the
rest of the line with R. Similar remarks apply to inserting and deleting
lines.                
*/
 
 
:- needs
    append / 3,
    assertion / 2,
    assertion / 3,
    chars_to_tokens / 2,
    for / 2,
    is_digit_char / 2,
    is_minus_char / 1,
    is_newline_char / 1,
    max / 3,
    process_sentence / 2,
    rev_append / 3,
    token_error_details / 3.
 
 
:- pop_compile( 'screen_edit.p' ).
/*  Pop components. These read and write "raw" characters, bypassing
    the operating system buffer.
*/
 
 
/*
Main edit predicate.
--------------------
*/
 
 
/*  edit( List0+, Result- ):
        This is the main screen-editing predicate. It displays the
        characters in List0 and invokes the screen editor, returning
        when the user does a quit or exit.
*/
edit( Chars0, Chars ) :-
    list_to_block( Chars0, Block ),
    Buffer0 = buffer( Block, 1, 1, 1, 1 ),
    set_edit_mode,
    draw_screen( Buffer0 ),
    get_edit_char( C ),
    edit( C, Buffer0, FinalState ),
    (
        FinalState = quit
    ->
        Chars = quit
    ;
        assertion( FinalState=use(FinalBuffer), 'edit' ),
        FinalBuffer = buffer( FinalBlock, _, _, _, _ ),
        block_to_list( FinalBlock, Chars )
    ),
    set_normal_mode.
 
 
/*
Converting from lists to edit buffers.
--------------------------------------
 
Character lists almost but not quite have the syntax
    List = {segment nl}*
where segment is a sequence of characters that does not include a
newline, and nl is a newline. There need not be a newline after the
final segment; and if there are any before the first non-null segment,
or after the final one, we want to ignore them. This might not be
acceptable in a more general text editor, but here such blank lines are
quite unnecessary.
 
The main predicate defined here is
    block_to_list( List+, Block- )
which does the conversion. It is not very elegant, because of the
need to differentiate between
    a b c d nl nl nl e
and
    a b c d nl nl nl
In the first case, our block must have a blank line between each newline;
but we don't want these in the second case.
 
I have not thought hard about how to optimise the code or make it more
elegant.              
*/
 
 
/*  list_to_block( L+, B- ):
        L is a list of characters. Convert it to an edit block in B.
*/
list_to_block( L, block([],B) ) :-
    list_to_block_1( L, B_ ),
    (
        B_ = [line([],[],1)]
    ->
        B = [line([],[32],1)]
        /*  Maintain Right-not-empty invariant.  */
    ;
        B = B_
    ).
 
 
list_to_block_1( L, B ) :-
    list_seg( L, Pre, Newlines, Post ),
    Line1 = line([],Pre,1),
    (
        ( Newlines = eof ; Post = [] )
    ->
        B = [Line1]
    ;
        list_to_block_1( Post, RestBuffer ),
        Empties is Newlines - 1,
        prepend_blank_lines( Empties, RestBuffer, RestAndEmptyLines ),
        append( [Line1], RestAndEmptyLines, B )
    ).
 
 
/*  prepend_blank_lines( N+, B+, BPrefixed- ):
        B is a list of lines. prepend_blank_lines sticks N empty lines
        in front of it, giving BPrefixed.
*/
prepend_blank_lines( 0, B, B ) :- !.
 
prepend_blank_lines( N0, B, [ line([],L,1) | B1 ] ) :-
    N1 is N0-1,
    prepend_blank_lines( N1, B, B1 ).
 
 
/*  list_seg( L+, Pre-, N-, Post+ ):
        L = Pre + N newlines + Post.
        If L contains no newlines, N becomes eof and Post is undefined.
*/
list_seg( [], [], eof, [] ) :- !.
 
list_seg( [Newline|T], [], N, Post ) :-
    is_newline_char( Newline ),
    !,
    count_leading_chars( Newline, T, 1, N, Post ).
 
list_seg( [Other|T], [Other|T_], N, Post ) :-
    list_seg( T, T_, N, Post ).
 
 
/*  count_leading_chars( Char+, List+, N0+, N-, Post- ):
        Counts the number of leading Chars at the front of List,
        setting N to N0+the result, and Post to the rest of List.
 
        In other words, implements the relation
            List = (N-N0)*Char + Post.
*/
count_leading_chars( Char, [Char|T], N0, N, Post ) :-
    !,
    N1 is N0 + 1,
    count_leading_chars( Char, T, N1, N, Post ).
 
count_leading_chars( _, L, N, N, L ) :-
    !.
 
 
/*
Converting from edit buffers to lists.
--------------------------------------
 
This is easier than above. The main complication is that we have to
convert lines in reverse order (top_to_list) and normal order
(bottom_to_list); and characters in reverse order (via rev_append) and
normal order. Again, I have not tried to optimise this. It is fast
enough on our VAX.    
*/
 
 
/*  block_to_list( B+, List- ):
        Convert edit block B to List. List will contain one
        newline character after each line.
*/
block_to_list( block(T,B), List ) :-
    top_to_list( T, TList ),
    bottom_to_list( B, BList ),
    is_newline_char( Newline ),
    append( TList, [Newline|BList], List ).
 
 
top_to_list( [], [] ) :- !.
 
top_to_list( [line(L,R,_)|T], List ) :-
    rev_append( L, R, HList ),
    top_to_list( T, LT ),
    is_newline_char( Newline ),
    append( LT, [Newline|HList], List ).
 
 
bottom_to_list( [], [] ) :- !.
 
bottom_to_list( [line(L,R,_)|T], List ) :-
    bottom_to_list( T, LT ),
    rev_append( L, R, HList ),
    is_newline_char( Newline ),
    append( HList, [Newline|LT], List ).
 
 
/*
The main edit loop.
-------------------
 
This section defines editB/3, which takes a key structure and decides
which predicate to call to update the buffer and screen.
*/
 
 
/*  edit( Action+, Buffer0+, State- ):
        This predicate applies editing action Action to Buffer0, updates
        the screen accordingly, and if the action doesn't terminate the
        edit, then reads another character and repeats. It terminates on
        an exit or quit.
 
        If the edit eventually terminates by an exit, State is
        use(Buffer), where Buffer is the edited version of Buffer0.
        Otherwise, it is quit.
 
        It is assumed that the buffer before any changes has already
        been displayed. If this is not so, the call to update_screen
        will draw rubbish, since for efficiency, it may not redraw
        everything from scratch, but update part of the image in situ.
*/
edit( exit, Buffer0, use(Buffer0) ) :- !.
 
edit( quit, Buffer0, quit ) :- !.
 
edit( C, Buffer0, State ) :-
    editB( C, Buffer0, Buffer1, Action ),
    update_screen( Action, Buffer1 ),
    get_edit_char( NextC ),
    edit( NextC, Buffer1, State ).
 
 
/*  editB( Key+, Buffer+, Buffer_-, Action- ):      
        Buffer_ and Action are the new buffer and screen-update
        action produced by the effect of Key on Buffer.
*/
editB( left, Buffer, Buffer_, Action ) :-
    !,
    leftB( Buffer, Buffer_, Action ).
 
editB( right, Buffer, Buffer_, Action ) :-
    !,
    rightB( Buffer, Buffer_, Action ).
 
editB( up, Buffer, Buffer_, Action ) :-
    !,
    upB( Buffer, Buffer_, Action ).
 
editB( down, Buffer, Buffer_, Action ) :-
    !,
    downB( Buffer, Buffer_, Action ).
 
editB( newline, Buffer, Buffer_, Action ) :-
    !,
    insnlB( Buffer, Buffer_, Action ).
 
editB( delete, Buffer, Buffer_, Action ) :-
    !,
    delB( Buffer, Buffer_, Action ).
 
editB( refresh, Buffer, Buffer, refreshACT(Buffer) ) :- !.
 
editB( illegal, Buffer, Buffer, noneACT ) :- !.
/*  Just ignore illegal characters: silly to warn the user.  */
 
editB( other(C), Buffer, Buffer_, Action ) :-
    insB( C, Buffer, Buffer_, Action ).
 
 
/*
Cursor left.
------------
*/
 
 
leftB( buffer( Block, Xw, Yw, Xc, Yc ),
       buffer( Block_, Xw_, Yw_, Xc_, Yc_ ),
       Action
     ) :-
    left_buf( Block, Xc, Yc, Ch, Block_, Xc_, Yc_, Ch_ ),
    left_scr( Block_, Xc, Yc, Ch, Xc_, Yc_, Ch_, Xw, Yw, Xw_, Yw_, Action ).
 
 
left_buf( block( [], [line( [], [Rc1|Rt], Xc )|Bt] ), Xc, Yc, Rc1
          block( [], [line( [], [Rc1|Rt], Xc )|Bt] ), Xc, Yc, Rc1
        ) :- !.
/*  Do nothing if at start of text.  */
 
left_buf( block( [Tl1|Tt], [line( [], [Rc1|Rt], Xc )|Bt] ), Xc, Yc, Rc1,
          block( Tt, [ Tl1_, line( [], [Rc1|Rt], Xc )|Bt] ), Xc_, Yc_, 32
        ) :-
    !,
    /*  If at start of line, move to after last non-blank on previous
        line.
    */
    line_cursor_at_end( Tl1, Tl1_ ),
    Yc_ is Yc - 1,
    assertion( Tl1_ = line( _, [32], Xc_ ), 'left_buf' ).
 
left_buf( block( T, [line( [ Lc1 | Lt ], R, Xc )|Bt] ), Xc, Yc, Rc1
          block( T, [line( Lt, [ Lc1 | R ], Xc_ )|Bt] ), Xc_, Yc, Lc1
        ) :-
    /*  Otherwise, move one character left.  */
    Xc_ is Xc - 1,
    R = [Rc1|_].
 
 
left_scr( _, Xc, Yc, Ch, Xc_, Yc_, Ch_, Xw, Yw, Xw, Yw, Action ) :-
    in_screen_x( Xw, Xc_ ),
    in_screen_y( Yw, Yc_ ),
    !,
    /*  If the new cursor will be on the screen, move it without
        scrolling.
    */
    Action = cursor_moveACT( Xw, Yw, Xc, Yc, Ch, Xc_, Yc_, Ch_ ).
 
left_scr( Block_, 1, Yc, _, Xc_, Yc_, _, Xw, Yw, Xw_, Yw_, Action ) :-
    !,
    /*  If the new cursor would not be on the screen, and has moved up
        to the end of the previous line, scroll up if necessary. If
        necessary, also scroll left so that new cursor is on rightmost
        column in window.
    */
    ( not(in_screen_y(Yw,Yc_)) -> Yw_ is Yw-1 ; Yw_ = Yw ),
    (
        not( in_screen_x(Xw,Xc_) )
    ->
        window_length( WL ),
        Xw_ is Xc_ - WL
    ;
        Xw_ = Xw
    ),
    Action = redraw_bufferACT( Xw_, Yw_, Xc_, Yc_, Block_ ).
 
left_scr( Block_, Xc, Yc, _, Xc_, Yc_, _, Xw, Yw, Xw_, Yw, Action ) :-
    !,
    /*  If the new cursor would not be on the screen, and has
        moved one place left, scroll left.
    */
    Xw_ is Xw - 1,
    Action = redraw_bufferACT( Xw_, Yw, Xc_, Yc_, Block_ ).
 
 
/*
Cursor right.
-------------
*/
 
 
rightB( buffer( Block, Xw, Yw, Xc, Yc ),
        buffer( Block_, Xw_, Yw, Xc_, Yc ),
        Action
      ) :-
    right_buf( Block, Xc, Ch, Block_, Xc_, Ch_ ),
    right_scr( Block_, Xc, Yc, Ch, Xc_, Ch_, Xw, Yw, Xw_, Action ).
 
 
right_buf( block( T, [line( L, [ Rc1 ], Xc )|Bt] ), Xc, Rc1,
           block( T, [line( [ Rc1 | L ], [32], Xc_ )|Bt] ), Xc_, 32
         ) :-
    !,
    /*  If at end of line, move right and add one blank to satisfy
        right-list invariant.
    */
    Xc_ is Xc + 1.
 
right_buf( block( T, [line( L, [ Rc1 | Rt ], Xc )|Bt] ), Xc, Rc1,
           block( T, [line( [ Rc1 | L ], Rt, Xc_ )|Bt] ), Xc_, Rc2
         ) :-
    /*  Move right.  */
    Xc_ is Xc + 1,
    first_char( Rt, Rc2 ).
 
 
right_scr( _, Xc, Yc, Ch, Xc_, Ch_, Xw, Yw, Xw, Action ) :-
    in_screen_x( Xw, Xc_ ),
    !,
    /*  If the new cursor will be on the screen, move it right.  */
    Action = cursor_alongACT( Xw, Yw, Xc, Yc, Ch, Xc_, Ch_ ),
    first_char( Rest, Ch_ ).
 
right_scr( Block_, Xc, Yc, _, Xc_, _, Xw, Yw, Xw_, Action ) :-
    !,
    /*  if the new cursor would not be on the screen, scroll right.  */
    Xw_ is Xw + 1,
    Action = redraw_bufferACT( Xw_, Yw, Xc_, Yc, Block_ ).
 
 
/*
Cursor up.
----------
*/
 
 
upB( buffer( Block, Xw, Yw, Xc, Yc ),
     buffer( Block_, Xw, Yw_, Xc, Yc_ ),
     Action
   ) :-
   up_buf( Block, Xc, Yc, Ch, Block_, Yc_, Ch_ ),
   up_scr( Block_, Xc, Yc, Ch, Yc_, Ch_, Xw, Yw, Yw_, Action ).
 
 
up_buf( block( [], B ), Xc, Yc, Rc1,
        block( [], B ), Yc, Rc1
      ) :-
    !,
    /*  Do nothing if on first line.  */
    B = [Rc1|_].
 
up_buf( block( [ Tl1 | Tt ], B ),  Xc, Yc, Ch,
        block( Tt, [ Tl1_ | B ] ), Yc_, Ch_
      ) :-
    /*  Move up one line.  */
    Yc_ is Yc - 1,
    line_cursor_at( Tl1, Xc, Tl1_ ),
    B = [ line(_,[Ch|_],_) | _ ],
    Tl1_ = line( _, [Ch_|_], _ ).
 
 
up_scr( _, _, 1, _, _, _, _, Yw, Yw, noneACT ) :- !.
    /*  Do nothing if on first line.  */
 
up_scr( _, Xc, Yc, Ch, Yc_, Ch_, Xw, Yw, Yw, Action ) :-
    in_screen_y( Yw, Yc_ ),
    !,
    /*  If the new cursor will be on the screen, move it up.  */
    Action = cursor_moveACT( Xw, Yw, Xc, Yc, Ch, Xc, Yc_, Ch_ ).
 
up_scr( Block_, Xc, Yc, _, Yc_, _, Xw, Yw, Yw_, Action ) :-
    !,
    /*  If the new cursor would not be on the screen,
        scroll up.
    */
    Yw_ is Yw - 1,
    Action = redraw_bufferACT( Xw, Yw_, Xc, Yc_, Block_ ).
 
 
/*
Cursor down.
------------
*/
 
 
downB( buffer( Block, Xw, Yw, Xc, Yc ),
       buffer( Block_, Xw, Yw_, Xc, Yc_ ),
       Action
     ) :-
   down_buf( Block, Xc, Yc, Ch, Block_, Yc_, Ch_ ),
   down_scr( Block_, Xc, Yc, Ch, Yc_, Ch_, Xw, Yw, Yw_, Action ).
 
 
down_buf( block( T, [Bl1] ), Xc, Yc, Ch,
          block( [ Bl1 | T ], [Bl2_] ), Yc_, 32
        ) :-
    !,
    /*  If on last line, create a new blank one below and move onto
        it.
    */
    Yc_ is Yc + 1,
    line_cursor_at( line([],[32],1), Xc, Bl2_ ),
    Bl1 = line(_,[Ch|_],_).
 
down_buf( block( T, [ Bl1, Bl2 | Bt ] ), Xc, Yc, Ch,
          block( [ Bl1 | T ], [ Bl2_ | Bt ] ), Yc_, Ch_
        ) :-
    /*  Move down one line.  */
    Yc_ is Yc + 1,
    line_cursor_at( Bl2, Xc, Bl2_ ),
    Bl1 = line(_,[Ch|_],_),
    Bl2_ = line(_,[Ch_|_],_).
 
 
down_scr( _, Xc, Yc, Ch, Yc_, Ch_, Xw, Yw, Yw, Action ) :-
    in_screen_y( Yw, Yc_ ),
    !,
    /*  If the new cursor will be on the screen,
        move it down one.
    */
    Action = cursor_moveACT( Xw, Yw, Xc, Yc, Ch, Xc, Yc_, Ch_ ).
 
down_scr( Block_, Xc, Yc, _, Yc_, _, Xw, Yw, Yw_, Action ) :-
    !,
    /*  If the new cursor would not be on the screen,
        scroll down.
    */
    Yw_ is Yw + 1,
    Action = redraw_bufferACT( Xw, Yw_, Xc, Yc_, Block_ ).
 
 
/*
Insert a newline.
-----------------
*/
 
 
insnlB( buffer( Block, Xw, Yw, Xc, Yc ),
        buffer( Block_, Xw_, Yw_, Xc_, Yc_ ),
        Action
      ) :-
   insnl_buf( Block, Xc, Yc, Block_, Xc_, Yc_ ),
   insnl_scr( Block_, Xc, Yc, Xc_, Yc_, Xw, Yw, Xw_, Yw_, Action ).
 
 
insnl_buf( block( T, [ line(L,R,Xc) | Bt ] ), Xc, Yc,
           block( [ line(L,[],Xc) | T ], [ line([],R,1) | Bt ] ), 1, Yc_
      ) :-
    /*  Split line at cursor.  */
    Yc_ is Yc + 1.
 
 
insnl_scr( Block_, Xc, Yc, Xc_, Yc_, Xw, Yw, Xw_, Yw_, Action ) :-
    ( not( in_screen_x(Xw,Xc_) ) ; not( in_screen_y(Yw,Yc_) ) ),
    !,
    /*  If new cursor not on screen, scroll down if necessary.
        Also scroll left if necessary so that cursor is on left
        hand column of window.
    */
    (
        not( in_screen_x(Xw,Xc_) )
    ->
        Xw_ = 1
    ;
        Xw_ = Xw
    ),
    (
        not( in_screen_y(Yw,Yc_) )
    ->
        Yw_ is Yw + 1
    ;
        Yw_ = Yw
    ),
    Action = redraw_bufferACT( Xw_, Yw_, Xc_, Yc_, Block_ ).
 
insnl_scr( block( [ line(L,[],Xc) | T ], [ line([],R,1) | Bt ] ), Xc, Yc, Xc_, Yc_, Xw, Yw, Xw, Yw, Action ) :-
    /*  Insert a newline in situ.  */
    Action = insert_newlineACT( Xw, Yw, Xc, Yc, R, Bt ).
 
 
/*
Delete.
-------
*/
 
 
delB( buffer( Block, Xw, Yw, Xc, Yc ),
      buffer( Block_, Xw_, Yw_, Xc_, Yc_ ),
      Action
    ) :-
   del_buf( Block, Xc, Yc, Block_, Xc_, Yc_ ),
   del_scr( Block_, Xc, Yc, Xc_, Yc_, Xw, Yw, Xw_, Yw_, Action ).
 
 
del_buf( block( [], [ line([],R,Xc) | Bt ] ), Xc, Yc
         block( [], [ line([],R,Xc) | Bt ] ), Xc  Yc
       ) :- !.
    /*  Do nothing if at start of text.  */
 
del_buf( block( [Tl1|Tt], [ line([],R,Xc) | Bt ] ), Xc, Yc
         block( Tt, [Tl1_|Bt] ), Xc_, Yc_
       ) :-
    !,
    /*  If at start of line, join it to previous line.  */
    line_to_left_list( Tl1, L, Xc__ ),
    strip_spaces_from_left_list( L, L_, N ),
    Xc_ is Xc__ - N,
    Tl1_ = line( L_, R, Xc_ ),
    Yc_ is Yc - 1.
 
del_buf( block( T, [ line([Lc1|Lt],R,Xc) | Bt ] ), Xc, Yc
         block( T, [ line(Lt,R,Xc_) | Bt ] ), Xc_, Yc
       ) :-
    /*  Delete character before cursor.  */
    Xc_ is Xc - 1.
 
 
del_scr( Block_, 1, 1, _, _, Xw, Yw, Xw, Yw, noneACT ) :- !.
    /*  Do nothing if at start of text.  */
 
del_scr( Block_, 1, Yc, Xc_, Yc_, Xw, Yw, Xw_, Yw_, Action ) :-
    ( not( in_screen_x(Xw,Xc_) ) ; not( in_screen_y(Yw,Yc_) ) ),
    !,
    /*  Cursor was at start of line, has jumped up and is no
        longer visible.  Scroll up and left if necessary as for
        left_scr.
    */
    ( not(in_screen_y(Yw,Yc_)) -> Yw_ is Yw-1 ; Yw_ = Yw ),
    (
        not(in_screen_x(Xw,Xc_))
    ->
        window_length( WL ),
        Xw_ is Xc_ + WL
    ;
        Xw_ = Xw
    ),
    Action = redraw_bufferACT( Xw_, Yw_, Xc_, Yc_, Block_ ).
 
del_scr( Block_, Xc, Yc, Xc_, Yc_, Xw, Yw, Xw_, Yw, Action ) :-
    not( in_screen_x(Xw,Xc_) ),
    !,
    /*  Cursor was not at start of line, has moved one place left and
        is no longer visible. Scroll left one.
    */
    Xw_ is Xw - 1,
    Action = redraw_bufferACT( Xw_, Yw, Xc_, Yc_, Block_ ).
 
del_scr( block( _, [ line( _, R, _ ) | Bt ] ), 1, Yc, Xc_, Yc_, Xw, Yw, Xw, Yw, Action ) :-
    !,
    /*  If at start of line, redraw portion including and below
        deleted line.
    */
    Action = delete_lineACT( Xw, Yw, Xc_, Yc_, R, Bt ).
 
del_scr( block( _, [ line(_,R,Xc_) | _ ] ), Xc, Yc, Xc_, Yc_, Xw, Yw, Xw, Yw, Action ) :-
    /*  Redraw line where character deleted.  */
    Action = delete_charACT( Xw, Yw, Xc_, Yc_, R ).
 
 
/*
Insert a character.
-------------------
*/
 
 
insB( C,
      buffer( Block, Xw, Yw, Xc, Yc ),
      buffer( Block_, Xw_, Yw, Xc_, Yc ),
      Action
    ) :-
    ins_buf( C, Block, Xc, Block_, Xc_ ),
    ins_scr( C, Block_, Xc, Yc, Xc_, Xw, Yw, Xw_, Action ).
 
 
ins_buf( C,
         block( T, [line( L, R, Xc )|Bt] ), Xc,
         block( T, [line( [ C | L ], R, Xc_ )|Bt] ), Xc_
       ) :-
    /*  Insert character before cursor.  */
    Xc_ is Xc + 1.
 
 
ins_scr( C, block( _, [line( [C|_], R, _ )|_] ), Xc, Yc, Xc_, Xw, Yw, Xw, Action ) :-
    in_screen_x( Xw, Xc_ ),
    !,
    /*  If the new cursor will be on the screen, redraw line with
        character inserted.
    */
    Action = insert_charACT( Xw, Yw, Xc, Yc, C, R ).
 
ins_scr( C, Block_, Xc, Yc, Xc_, Xw, Yw, Xw_, Action ) :-
    !,
    /*  If the new cursor would not be on the screen, scroll right.  */
    Xw_ is Xw + 1,
    Action = redraw_bufferACT( Xw_, Yw, Xc_, Yc, Block_ ).
 
 
/*
General line-handling.
----------------------
 
The predicates here deal with moving the cursor inside a line,
converting left-lists to right-lists, and so on.
*/
 
 
/*  line_to_left_list( Line+, L-, Xc- ):
        L is a left-list of all Line's characters, and Xc is the cursor
        position.
*/
line_to_left_list( line(L,R,Xc), L_, Xc_ ) :-
    line_to_left_list( R, L, Xc, L_, Xc_ ).
 
 
line_to_left_list( [], L, Xc, L, Xc ) :- !.
 
line_to_left_list( [Rc1|Rt], L, Xc, L_, Xc_ ) :-
    Xc__ is Xc + 1,
    line_to_left_list( Rt, [Rc1|L], Xc__, L_, Xc_ ).
 
 
/*  strip_spaces_from_left_list( L0+, L-, N- ):
        L is the result of removing all trailing spaces from left-list
        L0.
        N is the number of spaces removed.
*/
strip_spaces_from_left_list( [32|Rest], L, N ) :-
    !,
    strip_spaces_from_left_list( Rest, L, N_less_1 ),
    N is N_less_1 + 1.
 
strip_spaces_from_left_list( L, L, 0 ).
 
 
/*  first_char( C- ):
        C is the first character in a list, or blank if none. This is
        sometimes applied to portions of right-lists beyond the trailing
        blank, so can't assume that's there. (But maybe I should make
        them end in two blanks?).
*/
first_char( [], 32 ) :- !.
first_char( [C|_], C ).
 
 
/*  line_cursor_at( Line0+, Xc-, Line- ):
        Line is the result of moving Line0's cursor to Xc.
*/
line_cursor_at( line(L,R,Xc), Xc, line(L,R,Xc) ) :-
    !.
 
line_cursor_at( line([Lc1|Lt],R,X), Xc, line(L_,R_,Xc_) ) :-
    Xc < X,
    !,
    X_ is X - 1,
    line_cursor_at( line(Lt,[Lc1|R],X_), Xc, line(L_,R_,Xc_) ).
 
line_cursor_at( line(L,[Rc1],X), Xc, line(L_,R_,Xc_) ) :-
    Xc > X,
    !,
    X_ is X + 1,
    line_cursor_at( line([Rc1|L],[32],X_), Xc, line(L_,R_,Xc_) ).
 
line_cursor_at( line(L,[Rc1|Rt],X), Xc, line(L_,R_,Xc_) ) :-
    Xc > X,
    !,
    X_ is X + 1,
    line_cursor_at( line([Rc1|L],Rt,X_), Xc, line(L_,R_,Xc_) ).
 
 
/*  line_cursor_at_end( Line0+, Line- ):
        Line is the result of moving Line0's cursor to just after
        the last non-blank character.
*/
line_cursor_at_end( Line, line(L_,[32],Xc_) ) :-
    line_to_left_list( Line, L__, Xc__ ),
    strip_spaces_from_left_list( L__, L_, N ),
    Xc_ is Xc__ - N.
 
 
/*
Screen-update actions.
----------------------
*/
 
 
/*  update_screen( Action+, Buffer+ ):
        Update the image on the screen according to Action.
        The new image should be consistent with Buffer.
 
        A simple-minded version of this predicate could just
        redraw Buffer from scratch, ignoring the Action. We
        take the other approach, always using Action and ignoring
        Buffer.
*/
update_screen( Action, Buffer ) :-
    do_screen_actions( Action ).
 
 
/*
Redraw block:
Redraw the text, but not the frame.
*/
redraw_bufferACT( Xw, Yw, Xc, Yc, Block ) :-
    draw_buffer( buffer(Block,Xw,Yw,Xc,Yc) ).
 
 
/*
Refresh screen:
Buffer is the entire buffer.
Redraw the text and frame.
*/
refreshACT( Buffer ) :-
    draw_screen( Buffer ).
 
 
/*
Cursor right/left in line:
Move it from text coordinates (Xc,Yc) to (Xc_,Yc). The characters
at those coordinates are Ch and Ch_ respectively.
Top-left corner of window is at text coordinate (Xw,Yw).
*/
cursor_alongACT( Xw, Yw, Xc, Yc, Ch, Xc_, Ch_ ) :-
    flash_edit_cursor( Xw, Yw, Xc_, Yc ).
 
 
/*
Cursor move:
Move it from text coordinates (Xc,Yc) to (Xc_,Yc_). The characters
at those coordinates are Ch and Ch_ respectively.
Top-left corner of window is at text coordinate (Xw,Yw).
*/
cursor_moveACT( Xw, Yw, Xc, Yc, Ch, Xc_, Yc_, Ch_ ) :-
    flash_edit_cursor( Xw, Yw, Xc_, Yc_ ).
 
 
/*
Insert newline:
Insert a newline at text coordinate (Xc,Yc).
Make R the line below, with the cursor at (1,Yc+1), and
make B the block of text below that.
Top-left corner of window is at text coordinate (Xw,Yw).
*/
insert_newlineACT( Xw, Yw, Xc, Yc, R, B ) :-
    edit_cursor( Xw, Yw, Xc, Yc ),
    clear_to_end_of_line,
    Yc_ is Yc + 1,
    draw_right( Xw, Yw, 1, Yc_, R ),
    Yc__ is Yc_ + 1,
    draw_bottom( Yc__, B, Xw, Yw, _ ),
    flash_edit_cursor( Xw, Yw, 1, Yc_ ).
 
 
/*
Insert character:
Insert Ch at text coordinate (Xc,Yc), and insert R after it.
Move cursor to (Xc+1,Yc).
Top-left corner of window is at text coordinate (Xw,Yw).
*/
insert_charACT( Xw, Yw, Xc, Yc, Ch, R ) :-
    draw_right( Xw, Yw, Xc, Yc, [Ch|R] ),
    clear_to_end_of_line,
    Xc_ is Xc + 1,
    flash_edit_cursor( Xw, Yw, Xc_, Yc ).
 
 
/*
Delete line:
Delete the line at Yc by drawing R from (Xc,Yc), and B below that.
Move cursor to text coordinate (Xc,Yc).
Top-left corner of window is at text coordinate (Xw,Yw).
*/
delete_lineACT( Xw, Yw, Xc, Yc, R, B ) :-
    Yc_ is Yc + 1,
    draw_bottom( Yc_, B, Xw, Yw, Y_ ),
    Y__ is Y_ + 1,
    clear_to_end_of_window( Yw, Y__ ),
    draw_right( Xw, Yw, Xc, Yc, R ),
    flash_edit_cursor( Xw, Yw, Xc, Yc ).
 
 
/*
Delete character:
Delete the character at text coordinate (Xc,Yc) by drawing R
starting from that point.
Move cursor to (Xc,Yc).
Top-left corner of window is at text coordinate (Xw,Yw).
*/
delete_charACT( Xw, Yw, Xc, Yc, R ) :-
    draw_right( Xw, Yw, Xc, Yc, R ),
    clear_to_end_of_line,
    flash_edit_cursor( Xw, Yw, Xc, Yc ).
 
 
/*
Do nothing:
*/
noneACT :-
    true.
 
 
/*  do_screen_actions( A+ ):
        A is an action or list thereof; perform each one.
*/
do_screen_actions( [] ) :- !.
 
do_screen_actions( [Act|T] ) :-
    assertion( Act, 'do_screen_actions: action failed', [Act] ),
    do_screen_actions( T ).
 
do_screen_actions( Act ) :-
    assertion( Act, 'do_screen_actions: action failed', [Act] ).
 
 
/*
Screen updating.
----------------
 
This section deals with drawing on the screen.
*/
 
 
/*  draw_screen( Buffer+ ):
        Clear the screen, draw the lines and messages that delimit
        the edit area, and then draw Buffer inside the edit area.
*/
draw_screen( Buffer ) :-
    draw_frame,
    draw_buffer( Buffer ).
 
 
/*  draw_frame:
        Clear the screen and draw the lines and messages that delimit
        the edit area. The coordinates here must be consistent with
        those in the following section.            
*/
draw_frame :-
    clear_screen,
    cursor( 0, 0 ),
    put_raw_atom(
'                         You are in the screen editor.'
    ),
    cursor( 0, 1 ),
    is_minus_char( Minus ),
    for( 80, put_raw_char(Minus) ),
    cursor( 0, 21 ),
    for( 80, put_raw_char(Minus) ),
    cursor( 0, 22 ),
    put_raw_atom(
'           To get out without making any changes, hit ENTER, then q.'
    ),
    cursor( 0, 23 ),
    put_raw_atom(
'             To get out and make your changes, hit ENTER, then x.'
    ).
 
 
/*  draw_buffer( Buffer+ ):
        Put the Buffer to the edit region. Don't redraw the frame.
*/
draw_buffer( buffer( block(T,B), Xw, Yw, Xc, Yc ) ) :-
    draw_top( 1, T, Xw, Yw, Y_ ),
    draw_bottom( Y_, B, Xw, Yw, Y__ ),
    clear_to_end_of_window( Yw, Y__ ),
    flash_edit_cursor( Xw, Yw, Xc, Yc ).
 
 
/*  draw_top( Y+, T+, Xw+, Yw+, Y_- ):
        Put top-list T to the screen.
        First line has text coordinate Y; Y_ will be the text coordinate
        of the first line below T. Window coordinates are (Xw,Yw).
*/
draw_top( Y, [], _, _, Y ) :-
    !.
 
draw_top( Y, [H|T], Xw, Yw, Y_ ) :-
    draw_top( Y, T, Xw, Yw, Y__ ),
    draw_line( Y__, H, Xw, Yw ),
    Y_ is Y__ + 1.
 
 
/*  draw_bottom( Y+, B+, Xw+, Yw+, Y_- ):
        Put bottom-list B to the screen.
        First line has text coordinate Y; Y_ will be the text coordinate
        of the first line below B. Window coordinates are (Xw,Yw).
*/
draw_bottom( Y, [], _, _, Y ) :-
    !.
 
draw_bottom( Y, [H|T], Xw, Yw, Y_ ) :-
    draw_line( Y, H, Xw, Yw ),
    Y__ is Y + 1,
    draw_bottom( Y__, T, Xw, Yw, Y_ ).
 
 
/*  draw_line( Y+, L+, Xw+, Yw+ ):
        Put line L to the screen. It has text coordinate Y.
        Window is at (Xw,Yw).
*/
draw_line( Y, L, Xw, Yw ) :-
    not( in_screen_y( Yw, Y ) ),
    !.
 
draw_line( Y, line(L,R,_), Xw, Yw ) :-
    !,
    rev_append( L, R, Line ),
    draw_right( Xw, Yw, 1, Y, Line ).
 
 
/*  draw_right( Xw+, Yw+, Xc+, Yc+, Line+ ):
        Put Line (a right-list, i.e. a list of characters in
        normal order) to the screen, at vertical text coordinate
        Yc, and with its first character at text coordinate Xc.
        The window is at (Xw,Yw).
*/
draw_right( Xw, Yw, Xc, Yc, Line ) :-
    slice_line( Line, Xw, Xc, XcStart, Slice ),
    edit_cursor( Xw, Yw, XcStart, Yc ),
    put_raw_list( Slice ),
    clear_to_end_of_line.
 
 
/*
Low-level screen layout.
------------------------
 
The code in this section relies on the fact that 'cursor' has an origin
of (0,0). In some places, it also relies on knowledge of the layout
defined in draw_frame.                     
*/
 
 
/*  window_length( WL- ):
        The number of columns in the window.
*/
window_length( 80 ).
 
 
/*  window_depth( WD- ):
        The number of rows in the window.
*/
window_depth( 19 ).
 
 
/*  edit_cursor( Xw+, Yw+, Xc+, Yc+ ):
        Move the cursor to the specified coordinates, so that future
        output begins there. Differs from flash_edit_cursor in that the
        latter has to make the move visible (i.e. to move a flashing
        block): edit_cursor need not.
 
        Note that edit_cursor relies on the fact that 'cursor' (the
        VT100 interface predicate) has an origin of (0,0), and that
        the edit region is displaced by two lines down to leave room
        for the top of the frame and a message.
*/
edit_cursor( Xw, Yw, Xc, Yc ) :-
    Xs is Xc - Xw,
    Ys is Yc - Yw + 2,
    cursor( Xs, Ys ).
 
 
/*  flash_edit_cursor( Xw+, Yw+, Xc+, Yc+ ):
        Move the cursor, as a flashing block (or however you want
        to make it visible) to the specified coordinates.
*/
flash_edit_cursor( Xw, Yw, Xc, Yc ) :-
    edit_cursor( Xw, Yw, Xc, Yc ).
 
 
/*  in_screen_x( Xw+, Xc+ ):
        Xc is in the window which starts at Xw.
*/
in_screen_x( Xw, Xc ) :-
    Xc >= Xw,
    window_length( WL ),
    Right is Xw + WL - 1,
    Xc =< Right.
 
 
/*  in_screen_y( Yw+, Yc+ ):
        Yc is in the window which starts at Yw.
*/
in_screen_y( Yw, Yc ) :-
    Yc >= Yw,
    window_depth( WD ),
    Bottom is Yw + WD - 1,
    Yc =< Bottom.
 
 
/*  clear_to_end_of_window( Yw+, Y+ ):
        Y is the text coordinate of the first line beyond the buffer.
        Clear as many lines as are necessary until reaching the end
        of the edit window. The window's Y coordinate is Yw.
*/
clear_to_end_of_window( Yw, Y ) :-
    not( in_screen_y(Yw,Y) ), !.
 
clear_to_end_of_window( Yw, Y ) :-
    clear_to_end_of_line,
    Y_ is Y + 1,
    clear_to_end_of_window( Yw, Y_ ).
 
 
/*  slice_line( Line+, Xw+, Xc+, XcStart-, Slice- ):
        Slice is that part of Line which lies inside the window.
        Line starts at Xc; XcStart is the text coordinate of the
        first character inside the window.
*/
slice_line( Line, Xw, Xc, XcStart, Slice ) :-
    Xs is Xc - Xw,
    window_length( WL ),
    XsHigh is WL - 1,
    /*  XsHigh = screen coordinate of rightmost window column.  */
    slice_line( Line, Xs, XsHigh, Slice ),
    max( Xs, 0, Xs_ ),
    min( Xs_, XsHigh, XsStart ),
    XcStart is XsStart + Xw.
 
 
slice_line( _, Xs, XsHigh, [] ) :-
    Xs > XsHigh,
    !.
 
slice_line( [], _, _, [] ) :- !.
 
slice_line( [_|T], Xs, XsHigh, Line ) :-
    Xs < 0,
    !,
    Xs1 is Xs + 1,
    slice_line( T, Xs1, XsHigh, Line ).
 
slice_line( [H|T], Xs, XsHigh, [H|Line] ) :-
    Xs1 is Xs + 1,
    slice_line( T, Xs1, XsHigh, Line ).
 
 
/*
Reading terminal input
----------------------
 
This part of the module defines 'get_edit_char(C)', which is responsible
for reading 'cursor-up' and other such keys, and translating them to
something the editor can understand easily. It's specific to Poplog and
VT100s, but should be fairly easy to convert to other systems.
 
The main predicate,
    get_edit_char( C- )
returns in C a representation of the next character or character
sequence typed at the terminal. The representation is specialised for
use with my screen editor, making things like cursor-up easy to
represent.
 
The key mapping we want to implement, on a VT100 or compatible terminal,
is this:
    KEY                                 TERM
    Cursor-up (numeric keypad 8)        up
    Cursor-down (numeric keypad 2)      down
    Cursor-left (numeric keypad 4)      left
    Cursor-left (numeric keypad 6)      right
    Enter-v                             refresh
    Enter-x                             exit
    Enter-q                             quit
    Return                              newline
    Delete                              delete
    Any other char < 32                 illegal
    Any other char >= 32                other(C)
 
The reason for choosing the enter-v, enter-x and enter-q sequences is
for compatibility with the Poplog screen editor Ved (students will later
go on to Ved, and I want them to use the same keys).
 
To do this, we have to realise that (for example) the cursor-up key is
actually sent as a sequence of control characters. On a VT100, it's sent
as five characters:
    Escape [ O M x
for which the ASCII codes are
    27 91 79 77 120
(see any VT100 manual). Now, on some Prolog systems, such as ESL
Prolog-2, the predicate 'get0' will, if you press cursor-up, return
successive characters of this sequence each time you call it.
 
On Poplog that's not the case, because the Poplog system and VAX/VMS
intervene. If I call
    ?- get0(C)
and hit cursor-up (i.e. numeric keypad 8), it returns 58, the ASCII code
for '8'. This behavour appears to be unchangeable, but luckily I can
escape into Pop-11. That's what the predicate get_raw_char(C), defined
below, is for. It calls my Pop-11 routine get_raw_char, which in turn
calls two Pop-11 system routines, vedscreenraw and rawcharin. Both these
are used in the Ved editor (Poplog users, see REF VEDMANUAL and search
for the string VEDSCREENRAW in it).
 
The first of these sets the terminal into a mode (keypad application
mode) where numeric keypad 8 is received as the escape sequence above,
rather than as ASCII for '8'. The second reads a character in 'raw'
mode, i.e. with minimum operating system or other intervention. The Ved
editor does the same just before it reads any terminal input.
 
Thus, if you were to call
    ?- get_raw_char(C),
       get_raw_char(D),
       get_raw_char(E),
       get_raw_char(F),
       get_raw_char(G).
and then press numeric keypad 8, the variables would get the following
values:
    C = 27
    D = 91
    E = 79
    F = 77
    G = 120
corresponding to the sequence
    Escape [ O M x
 
Once we've got 'get_edit_char' working, the rest is just a matter of
looking in the VT100 manual, and decoding the key sequences. The cursor
keys all start with something called SS3, which is DECspeak for Escape
O. The Enter key is SS3 M, and we need to decide whether it's followed
by a v, x, or q. Hence the sequence decoding predicates below. These are
written in the style of a transition network: the predicates
    <thing>_NODE( C0, C )
define a node in this network: if you go along the arc selected by
character C0, you'll eventually get to C.
 
To test, you can do
    test_get_edit_char :-
        get_edit_char( C ),
        write( C ), nl,
        test_get_edit_char.
*/
 
 
/*  get_raw_char  */
 
get_raw_char( C ) :-
    prolog_eval( apply(valof(get_raw_char)), C ).
 
 
/* get_edit_char */
 
get_edit_char( C ) :-
    get_raw_char( C1 ),
    char1_NODE( C1, C ).
 
 
char1_NODE( /*escape*/      27,  C ) :-
    !,
    get_raw_char( C2 ),
    escape_NODE( C2, C ).
char1_NODE( /*return*/      13,  newline ) :- !.
char1_NODE( /*delete*/      127, delete ) :- !.
char1_NODE( /*unprintable*/ C,   illegal ) :-
    C < 32, !.
char1_NODE( /*any other*/   C,   other(C) ).
 
 
escape_NODE( /*O*/         79, C ) :-
    !,
    get_raw_char( C3 ),
    ss3_NODE( C3, C ).
escape_NODE( /*any other*/ C,  illegal ).
 
 
ss3_NODE( /*M*/     77,  C ) :-
    !,
    get_raw_char( C4 ),
    enter_NODE( C4, C ).
ss3_NODE( /*x*/     120, up ) :- !.
ss3_NODE( /*r*/     114, down ) :- !.
ss3_NODE( /*v*/     118, right ) :- !.
ss3_NODE( /*t*/     116, left ) :- !.
ss3_NODE( /*other*/ C,   illegal ).
 
 
enter_NODE( /*x*/     120, exit ) :- !.
enter_NODE( /*q*/     113, quit ) :- !.
enter_NODE( /*v*/     118, refresh ) :- !.
enter_NODE( /*other*/ C,   illegal ).
 
 
/*
Cursor control.
---------------
 
This is for VT100 compatible terminals. See any VT-100 handbook for the
meaning of the control codes. 
*/
 
 
/*  set_edit_mode:
        Place VDU into whatever mode is needed for screen-editing, i.e.
        it displays characters immediately, in the full 0-255 ASCII
        range.
*/
set_edit_mode :-
    prolog_eval( apply(valof(set_edit_mode)) ).
 
 
/*  set_normal_mode:
        Place VDU into its normal mode, if this is different from the
        edit mode.
*/
set_normal_mode :-
    prolog_eval( apply(valof(set_normal_mode)) ).
 
 
/*  clear_screen:
        Clear the screen.
        Moves cursor to screen coordinate (0,0).
*/
clear_screen :-
    escsq( '2J' ),
    cursor( 0, 0 ).
 
 
/*  clear_to_end_of_line:
        Clear to end of current line.
        Does not move cursor.
*/
clear_to_end_of_line :-
    escsq('K').
 
 
/*  clear_to_end_of_line( X+, Y+ ):
        Move cursor to screen coordinate (X,Y) and clear rest of line.
*/
clear_to_end_of_line( X, Y ) :-
    cursor( X, Y ),
    escsq('K').
 
 
/*  clear_to_end_of_screen:
        Clear rest of screen.
        Does not move cursor.
*/
clear_to_end_of_screen :-
    escsq('J').
 
 
/*  clear_to_end_of_screen( X+, Y+ ):
        Move cursor to screen coordinate (X,Y) and clear rest of screen.
*/
clear_to_end_of_screen( X, Y ) :-
    cursor( X, Y ),
    escsq('J').
 
 
/*  cursor( X+, Y+ ):
        Move cursor to screen coordinate (X,Y).
*/
cursor( X, Y ) :-
    escsq,
    Y1 is Y + 1, put_raw_integer( Y1 ),
    put_raw_char( 59 /* ; */ ),
    X1 is X + 1, put_raw_integer( X1 ),
    put_raw_char( 72 /* H */ ).
 
 
/*  highlight_on:
        Inverse video on.
*/
highlight_on :-
    escsq( '7m' ).
 
 
/*  highlight_off:
        Inverse video off.
*/
highlight_off :-
    escsq( '0m' ).
 
 
/*  escsq( X+ ):
        Emit atom X, preceded by an ESC and a [.
*/
escsq( X ) :-
    put_raw_char(27), put_raw_char(91), put_raw_atom( X ).
 
 
/*  escsq:
        Emit an ESC and a [.
*/
escsq :-
    put_raw_char(27), put_raw_char(91).
 
 
/*
Reading back from the editor.
-----------------------------
 
This is done in the same way as in MAIN_LOOP.PL. I have not attempted to
use the same predicate for both, because you might want to change the
editor's behaviour so it differs from the way the Tutor reads from the
terminal.
 
There may be a problem for some Prologs here. There is a
module-dependency loop: read_back calls process_sentence (from
MAIN_LOOP) which calls predicates from COMMANDS, which calls
SCREEN_EDIT. I am not sure how to restructure the program so as to avoid
this. Also, the token-error handling ought to be cleaned up.
 
Note that it is different from in MAIN_LOOP. The entire buffer is
tokenised before any parsing is done; if a tokenisation error is
detected, we stop immediately.
*/
 
 
read_back( Chars, ok ) :-
    chars_to_tokens( Chars, Tokens ),
    read_edit_sentences( Tokens ).
 
read_back( _, token_error(Message,Input) ) :-
    token_error_details( Message, Input, _ ).
 
 
/*  read_edit_sentences( Tokens+ ):
        Extract sentences one-by-one from Tokens, and process them.
 
        This actually allows commands as well as facts and questions.
        Perhaps it shouldn't?
*/
read_edit_sentences( [] ) :- !.
 
read_edit_sentences( Tokens ) :-
    extract_edit_sentence( Tokens, NextSentence, ItsTerminator, TokensLeft ),
    process_sentence( NextSentence, ItsTerminator ),
    read_edit_sentences( TokensLeft ).
 
 
extract_edit_sentence( [atom(T)|Rest], [], T, Rest ) :-
    ( T = '.' ; T = '?' ),
    /*  if T is a sentence terminator  */
    !.
 
extract_edit_sentence( [Token|RestOfTokens], [Token|RestOfSentence],
                       T, TokensAfterSentence
                     ) :-
    !,
    extract_edit_sentence( RestOfTokens, RestOfSentence, T,
                           TokensAfterSentence
                         ).
 
extract_edit_sentence( [], [], eof, [] ) :- !.
 
 
/*
Outputting raw characters.
--------------------------
 
The predicates below output characters, atoms, lists, and integers to
the terminal, bypassing any terminal buffer. As they stand, they output
one character at a time. However, if your system has facilities for
outputting an entire string at one go, it would be more efficient to use
that for put_raw_atom.
 
This part of the editor is non-portable, and calls Pop-11.      
*/
 
 
/*  put_raw_char( C+ ):
        Put C to the terminal.
*/
put_raw_char(C) :-
    prolog_eval( put_raw_char(C) ).
 
 
/*  put_raw_atom( A+ ):
        Put A to the terminal.
*/
put_raw_atom( A ) :-
    prolog_eval( put_raw_string(A) ).
 
 
/*  put_raw_integer( I+ ):
        Convert I to a minimum-width representation, and put it. I
        must be non-negative.
        Used for outputting the integers in cursor-control sequences.
*/
put_raw_integer( I ) :-
    int_to_list( I, L ),
    put_raw_list( L ).
 
 
/*  put_raw_list( L+ ):
        Put each element of L, assumed a character code, to the terminal.
*/
put_raw_list( L ) :-
    prolog_eval( put_raw_list(L) ).
 
 
/*  int_to_list( I+, L- ):
        L is the sequence of character codes representing I.
*/
int_to_list( A, [C] ) :-
    A =< 9,
    !,
    is_digit_char( C, A ).
 
int_to_list( A, L ) :-
    int_to_list( A, [], L ).
 
 
int_to_list( 0, Sofar, Sofar ) :- !.
 
int_to_list( I, Sofar, L ) :-
    Rem is I mod 10,
    Div is I div 10,
    is_digit_char( C, Rem ),
    int_to_list( Div, [C|Sofar], L ).
 
 
:- endmodule.
