-- PROGRAM EDITOR IMPLEMENTATION -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

DIRECTORY
BOPPictures USING [Point],
MUMParser USING [SymbolCode, ...],
MUMEditor,
Rope USING [Ref, FromString, FromChar, Concat, Substr, Replace, Size],
BOPBooks USING [.., MakeIntoBook, ..],
BOPKeys USING [SP, CR, TAB, BS, DEL, MouseCharacters, GetChar, TimeInterval, DefineKeyCodes, SetUpStandardTables],
BOPTTY USING [RefTTY, MakeTTY, DefaultTTY, BlindPutChar, BackChar, NewLine, Clear],
BOPSetUp USUNG [SetUpBWDisplay, SetUpColorDisplay, ConnectColorDisplay],
BOPWindows USING [SetCursor, GetCursor, Zero];
MUMEditorImpl: PROGRAM
IMPORTS MUMParser, BOPKeys, BOPTTY, BOPBooks, BOPWindows, Rope
EXPORTS MUMEditor =
BEGIN OPEN BOPPictures, MUMParser, MUMEditor, BOPKeys, BOPTTY, BOPWindows;
-- Returns the edit point one character before EP, backing up to previous lines if neccessary. If there are no characters before EP, returns the first point in the source.
PrevChar: PROC [EP: EditPoint] RETURNS [NEP: EditPoint] =
BEGIN
NEP ← EP;
WHILE NEP.Col = 0 DO
IF NEP.Line.Prev = NIL THEN RETURN;
NEP.Line ← NEP.Line.Prev;
NEP.Col ← Rope.Size [NEP.Line.Text]
ENDLOOP;
NEP.Col ← NEP.Col - 1
END;
-- Returns the endpoint of the previous line, if it exists and EP is at the beginning of the curent one. Otherwise, returns the beginning of the current line.
PrevLine: PROC [EP: EditPoint] RETURNS [NEP: EditPoint] =
BEGIN
NEP ← EP;
IF NEP.Col # 0 AND NEP.Line.Prev # NIL THEN
NEP ← [NEP.Line.Prev, 0]
ELSE
NEP.Col ← 0
END;
-- PRELIMINARY VERSION - EP is always after last character.
EditSource: PUBLIC PROCEDURE [Src: EditableSource, T: RefTTY] RETURNS [EditableSource] =
BEGIN
EP: EditPoint;
C: CHAR; Ms: Point; Tm: TimeInterval;
Home: Point = T.Margins.Low;

IF Src.First = NIL THEN
{L: REF EditableLine = NEW [EditableLine ← [Home, Home, NIL, NIL, NIL]];
Src ← [L, L]};
EP ← ShowLines [Src.First, Home, T];
DO
[C, Ms, Tm] ← GetChar [];
SELECT TRUE FROM
C = BS => [Src, EP] ← DeleteChars [Src, PrevChar [EP], EP, T];
C = ConBS => EP ← PrevChar [EP];
C = DEL => [Src, EP] ← DeleteChars [Src, PrevLine [EP], EP, T];
C = CR => [Src, EP] ← BreakLine [Src, EP, T];
C = ConQ => RETURN [Src];
C IN [RedDown..ShiftBlueDown] =>
[Src, EP] ← InsertPoint [Src, EP, Ms, T];
ENDCASE => [Src, EP] ← InsertChar [Src, EP, C, T]
ENDLOOP
END;
PointOpen: Rope.Ref = Rope.FromString ["[I: "];
PointSep: Rope.Ref = Rope.FromString [", J: "];
PointClose: Rope.Ref = Rope.FromString ["]"];
InsertPoint: PROC [Src: EditableSource, EP: EditPoint, P: Point, T: RefTTY] RETURNS [New: EditableSource, NEP: EditPoint] =
BEGIN
R: Rope.Ref ← Rope.Concat [PointOpen, IntToRope [P.I]];
R ← Rope.Concat [R, PointSep];
R ← Rope.Concat [R, IntToRope [P.J]];
R ← Rope.Concat [R, PointClose];
[New, NEP ] ← ReplaceChars [Src, EP, EP, R, T]
END;
InsertChar: PROC [Src: EditableSource, EP: EditPoint, C: CHAR, T: RefTTY] RETURNS [New: EditableSource, NEP: EditPoint] =
BEGIN
[New, NEP ] ← ReplaceChars [Src, EP, EP, Rope.FromChar [C], T]
END;
InsertRope: PROC [Src: EditableSource, EP: EditPoint, R: Rope.Ref, T: RefTTY] RETURNS [New: EditableSource, NEP: EditPoint] =
BEGIN
[New, NEP ] ← ReplaceChars [Src, EP, EP, R, T]
END;
DeleteChars: PROC [Src: EditableSource, BegP, EndP: EditPoint, T: RefTTY] RETURNS [New: EditableSource, NEP: EditPoint] =
BEGIN
[New, NEP ] ← ReplaceChars [Src, BegP, EndP, NIL, T]
END;
ReplaceChars: PROC [Src: EditableSource, BegP, EndP: EditPoint, R: Rope.Ref, T: RefTTY] RETURNS [New: EditableSource, NEP: EditPoint] =
BEGIN OPEN Rope;
NewPos: Point;
Rest: Rope.Ref ← Substr [BegP.Line.Text, BegP.Col];
SetCursor [T.Window, BegP.Line.LastCP, Text];
BackRope [Rest, T];
Rest ← Substr [EndP.Line.Text, EndP.Col];
BegP.Line.Text ← Replace [BegP.Line.Text, BegP.Col, , Concat [R, Rest]];
BlindPutRope [R, T];
NewPos ← GetCursor [T.Window, Text];
BlindPutRope [Rest, T];
BegP.Line.LastCP ← GetCursor [T.Window, Text];
IF EndP.Line # BegP.Line THEN
Src ← DeleteLines [Src, BegP.Line.Next, EndP.Line];
SetCursor [T.Window, NewPos, Text];
RETURN [Src, [BegP.Line, BegP.Col + Size [R]]]
END;
-- Deletes lines LineA thru LineB inclusive from Src. May return an empty list. Moves the window cursor to a random point.
DeleteLines: PROC [Src: EditableSource, LineA, LineB: REF EditableLine, T: RefTTY] RETURNS [New: EditableSource] =
BEGIN
New ← Src;
IF LineA.Prev # NIL THEN
LineA.Prev.Next ← LineB.Next
ELSE
New.First ← LineB.Next;
IF LineB.Next # NIL THEN
{LineB.Next.Prev ← LineA.Prev;
[] ← ShowLines [LineB.Next, LineA.FirstCP, T]}
ELSE
New.Last ← LineA.Prev;
END;
BreakLine: PROC [Src: EditableSource, EP: EditPoint, T: RefTTY] RETURNS [New: EditableSource, NEP: EditPoint] =
BEGIN OPEN Rope;
Rest: Rope.Ref ← Substr [EP.Line.Text, EP.Col];
NewPos: Point;
Lin: REF EditableLine = NEW [EditableLine ←
[FirstCP: , LastCP: ,
Text: Rest,
Prev: EP.Line, Next: EP.Line.Next]];
SetCursor [T.Window, EP.Line.LastCP, Text];
BackRope [Rest, T];
EP.Line.Text ← Substr [EP.Line.Text, 0, EP.Col];
IF EP.Line.Next # NIL THEN
EP.Line.Next.Prev ← Lin
ELSE
Src.Last ← Lin;
EP.Line.Next ← Lin;
NewLine [T]; NewPos ← GetCursor [T.Window, Text];
[] ← ShowLines [Lin, NewPos, T];
SetCursor [T.Window, NewPos, Text];
RETURN [Src, [Lin, 0]]
END;
-- Erases the tty window below point P, and writes the lines from L on starting at P. Updates the FirstCP and LastCP fields in the lines and returns and editpoint at the end of the last line. Assumes L non-NIL.
ShowLines: PROC [L: REF EditableLine, P: Point, T: RefTTY] RETURNS [EditPoint] =
BEGIN
SetCursor [T.Window, P, Text];
KillLines [T];
DO
L.FirstCP ← GetCursor [T.Window, Text];
BlindPutRope [L.Text, T];
L.LastCP ← GetCursor [T.Window, Text];
IF L.Next = NIL THEN RETURN [[L, Rope.Size [L.Text]]];
NewLine [T];
L ← L.Next
ENDLOOP
END;
CleanSource: PUBLIC PROCEDURE [Src: EditableSource] RETURNS [TP: TextPt] =
BEGIN
EndingChar: TYPE = {Space, LetNum, String, Other};
State: EndingChar ← Other;
AppendChar: PROC [CC: CHAR] =
INLINE {TP.Text ← Concat [TP.Text, FromChar [SP]]};
Collect: PROC = INLINE {};
Skip: PROC =
INLINE {IF Beg < Pos THEN
{TP.Text ← Concat [TP.Text, Substr [L.Text, Beg, Pos - Beg]];
Beg ← Pos + 1}};
L: REF EditableLine ← Src.First;
Beg, Pos, End: Rope.Int;
C: CHAR;
TP.Text ← NIL;
WHILE L # NIL DO
Beg ← Pos ← 0; End ← Size [L.Text];
WHILE Pos < End DO
C ← Fetch [Line.Text, Pos];
SELECT TRUE FROM
State = String =>
{Collect; IF C = ’" THEN State ← Other};
C = TAB OR C = CR =>
{Skip; IF State = LetNum THEN
{AppendChar [SP]; State ← Space}};
C = SP =>
{IF State = LetNum THEN
{Collect; State ← Space}
ELSE Skip};
C IN [’0..’9] OR C IN [’a..’z] OR C IN [’A..’Z] =>
{Collect; State ← LetNum};
C = ’" =>
{Collect; State ← String};
ENDCASE => {Collect; State ← Other};
Pos ← Pos + 1
ENDLOOP;
Skip;
L ← L.Next
ENDLOOP;
TP.Pos ← 0; TP.Length ← Size [TP.Text]
END;
END...