-- file: EditorMain.Mesa
-- edited by Schroeder, January 25, 1980 9:24 AM
-- edited by Brotz, November 12, 1981 4:09 PM
-- edited by Levin, February 22, 1980 12:42 PM

DIRECTORY
Ascii USING [BS, ControlW, DEL, ESC],
dsD: FROM "DisplayDefs" USING [ChangeCursor, CursorShape, GetCursor],
Editor USING [AcceptTypeIn, BracketCommand, ButlersBoundaryCommand, cancelCode,
ClearSourceSelection, ComKey, CommandType, DeUnderlineSelection, FCommand,
GCommand, insertDeletionCode, InsertFromLastCommand, InsertLastDeletion, nextCode,
PCommand, Redo, RefreshFromFirstChange, SCommand, SelectEverything,
SelectNextBlank, shiftedSelectionFlag, ShiftKey, TCommand, UnderlineSelection, Undo,
UnrecognizedCommand],
exD: FROM "ExceptionDefs" USING [ClearExceptionsRegion, SysBug],
inD: FROM "InteractorDefs" USING [CaretIsBlinking, CharIndex, HousePtr,
KeyboardInputAcceptor, MessageTextNbrPtr, SetCaretBlinking, StopBlinkingCaret,
TextHouseRefresher, TextSelection, TextSelectionPtr],
intCommon USING [actionPoint, commandMode, commandType, composedMessageEdited,
deliverCommandHouse, deliverCommandVisible, editorType, newTargetSelection,
pendingCommandType, runCommandMode, secondarySelectionEnabled, source, target],
String USING [AppendString, LowerCase],
vmD: FROM "VirtualMgrDefs" USING [CharIndex, ComposedMessage,
ComposedMessagePtr, DeleteRangeInMessage, DisplayMessage, GetMessageSize,
InsertRangeInMessage, MessageRange, ReplaceRangeInMessage, UnlockTOC];

EditorMain: PROGRAM
IMPORTS dsD, Editor, exD, inD, intC: intCommon, String, vmD
EXPORTS Editor =

BEGIN
OPEN Editor, inD;

-- Editor Department of the Interactor Division
-- Implements the editor for the composed message. Commands are: Insert,
-- Append, Replace, Delete, Undo, Redo. Other operations (which are not
-- commands per se) are selection, scrolling, and command aborting.


-- Selection convention: a range is represented by a nonempty half open
-- interval; a point is represented by an empty half open interval; selection
-- beyond the message end is represented by the point [messageLength ..
-- messageLength).

-- Private global variables --

modelessEditor: BOOLEAN = (intC.editorType = modeless);
deleteChar: CHARACTER = IF modelessEditor THEN Ascii.DEL ELSE ’d;
target: TextSelectionPtr = @intC.target;
source: TextSelectionPtr = @intC.source;
secondarySelectionEnabled: POINTER TO BOOLEAN = @intC.secondarySelectionEnabled;



Decode: PUBLIC KeyboardInputAcceptor =
-- PROCEDURE [mnp: MessageTextNbrPtr, char: CHARACTER]
-- Dispatches to specific routines for handling a specific editor command. Fixes up scratch
-- buffer and lastCommand upon return of any of the command routines.
BEGIN
commandMode: POINTER TO BOOLEAN = @intC.commandMode;
savedUpperLeftCharIndex: CharIndex ← mnp.lines.firstCharIndex;
savedCursor: dsD.CursorShape;
interpretChar: BOOLEAN
← char = Ascii.ESC OR char = Ascii.DEL OR char = shiftedSelectionFlag
OR (IF ~modelessEditor THEN commandMode↑
ELSE ComKey[down] OR char = cancelCode
OR char = insertDeletionCode OR char = nextCode);

IF ~mnp.haveMessage AND char # shiftedSelectionFlag
AND ~(interpretChar AND (char IN [’0 .. ’9])) THEN RETURN;
exD.ClearExceptionsRegion[];
[savedCursor, , ] ← dsD.GetCursor[];
StopBlinkingCaret[];
IF interpretChar THEN
BEGIN
char ← String.LowerCase[char];
SELECT char FROM
’i => StartInsertionCommand[mnp, insert];
’a => StartInsertionCommand[mnp, append];
’d, deleteChar => DCommand[mnp];
Ascii.DEL => AbortTypeinCommand[mnp]; -- only possible if modal editor.
’r => RCommand[mnp];
’u, cancelCode => Undo[mnp];
’g => GCommand[mnp];
’p => PCommand[mnp];
-- Editor.shiftedSelectionFlag (untypeable) is flag for shifted insertion.
shiftedSelectionFlag => InsertSourceSelection[mnp];
Ascii.ESC => BEGIN
IF commandMode↑ OR modelessEditor THEN Redo[mnp]
ELSE IF secondarySelectionEnabled↑ THEN
BEGIN
IF source.end = 0 THEN InsertFromLastCommand[mnp]
ELSE InsertSourceSelection[mnp];
END
ELSE TerminateInsertion[mnp];
commandMode↑ ← TRUE;
secondarySelectionEnabled↑ ← FALSE;
END;
Ascii.BS =>
IF modelessEditor THEN AcceptTypeIn[mnp, Ascii.ControlW]
ELSE UnrecognizedCommand[];
’e => SelectEverything[mnp];
nextCode => IF modelessEditor THEN SelectNextBlank[mnp, ShiftKey[up]]
ELSE UnrecognizedCommand[];
insertDeletionCode =>
IF modelessEditor THEN InsertLastDeletion[mnp] ELSE UnrecognizedCommand[];
’s => SCommand[];
’f => FCommand[];
’t => TCommand[mnp];
’b, ’’, ’", ’[, ’<, ’(, ’{, ’- => BracketCommand[mnp, char];
IN [’0 .. ’9] => ButlersBoundaryCommand[char - ’0];
ENDCASE => {CancelSourceSelection[mnp]; UnrecognizedCommand[]};
END
ELSE BEGIN
CancelSourceSelection[mnp];
AcceptTypeIn[mnp, char];
IF modelessEditor THEN TurnOnDeliver[];
END;

dsD.ChangeCursor[savedCursor];
intC.runCommandMode ← FALSE;
IF CaretIsBlinking[] THEN SetCaretBlinking[target.point, mnp];
END; -- of Decode --


StartInsertionCommand: PROCEDURE [mnp: MessageTextNbrPtr, cType: CommandType] =
BEGIN
target.point ← IF cType = insert THEN target.start ELSE target.end;
IF ~modelessEditor THEN
BEGIN
intC.commandMode ← FALSE;
secondarySelectionEnabled↑ ← TRUE;
intC.pendingCommandType ← cType;
END;
IF target.pendingDelete THEN {CancelTargetSelection[]; DoTargetSelection[]};
END; -- of StartInsertionCommand --


DCommand: PROCEDURE [mnp: MessageTextNbrPtr] =
-- Deletes the selected text from the composed message.
BEGIN
IF target.start # target.end THEN
BEGIN
Deleter[mnp];
ResetInsertionBuffer[mnp];
DoTargetSelection[];
END;
END; -- of DCommand --


AbortTypeinCommand: PROCEDURE [mnp: MessageTextNbrPtr] = INLINE
-- Processes a DEL in the modal editor
BEGIN
IF secondarySelectionEnabled↑ THEN
BEGIN
CancelSourceSelection[mnp];
-- abort pending command.
IF intC.pendingCommandType = noCommand THEN exD.SysBug[];
IF intC.pendingCommandType = replace THEN Undo[mnp];
intC.pendingCommandType ← noCommand;
END
ELSE IF intC.commandMode THEN UnrecognizedCommand[]
ELSE TerminateInsertion[mnp];
intC.commandMode ← TRUE;
END; -- of AbortTypeinCommand --


RCommand: PROCEDURE [mnp: MessageTextNbrPtr] = INLINE
-- Initiates a Replace operation.
BEGIN
IF modelessEditor THEN DCommand[mnp]
ELSE BEGIN
IF intC.commandType = delete THEN
BEGIN -- save previous deletion for possible ’R ESC’ insertion.
SwapInsertionWithDeletionBuffer[mnp];
ResetDeletionBuffer[mnp];
END;
Deleter[mnp];
intC.commandMode ← intC.newTargetSelection ← FALSE;
secondarySelectionEnabled↑ ← TRUE;
intC.pendingCommandType ← replace;
END;
END; -- of RCommand --


Deleter: PUBLIC PROCEDURE [mnp: MessageTextNbrPtr] =
-- Deletes primary selection from mnp.message. Sets new primary selection to the
-- null range at the start of the just used primary selection. Depends on caller
-- to adjust primary selection after return if default selection is desired.
BEGIN
start: CharIndex = target.start;
end: CharIndex = target.end;

target.pendingDelete ← FALSE;
vmD.ReplaceRangeInMessage
[to: vmD.MessageRange[0, vmD.GetMessageSize[mnp.deletionBuffer],
mnp.deletionBuffer], from: vmD.MessageRange[start, end, mnp.message]];
vmD.DeleteRangeInMessage[from: vmD.MessageRange[start, end, mnp.message]];
intC.actionPoint ← start;
intC.commandType ← delete;
target↑ ← TextSelection[mnp, start, start, start, 0, char, FALSE];
target.point ← start;
RefreshFromFirstChange[actionIndex: start, deletedChars: end - start,
insertedChars: 0, mnp: mnp];
TurnOnDeliver[];
END; -- of Deleter --


InsertSourceSelection: PROCEDURE [mnp: MessageTextNbrPtr] =
BEGIN
commandType: POINTER TO CommandType = @intC.commandType;
actionPoint: POINTER TO CharIndex = @intC.actionPoint;
start, end: CharIndex;
modalSecondaryInsertion: BOOLEAN = (~modelessEditor AND secondarySelectionEnabled↑);
sourceSel: TextSelection = source↑;
newInsertion: BOOLEAN = intC.newTargetSelection OR sourceSel.pendingDelete;
insertionLength: CARDINAL = sourceSel.end - sourceSel.start;
insertionBufferLength: CharIndex;
message: vmD.ComposedMessagePtr = vmD.ComposedMessage[mnp.message];

ClearSourceSelection[unlock: FALSE];
BEGIN -- for EXITS --
IF insertionLength = 0 OR ~mnp.haveMessage THEN GO TO UnlockSourceSel;
IF modalSecondaryInsertion THEN ResetInsertionBuffer[mnp];
IF modelessEditor AND newInsertion THEN ResetBuffers[mnp];
IF sourceSel.pendingDelete THEN
BEGIN
delStart, delEnd, targetStart, targetEnd, targetPoint: CharIndex;
overlap: CARDINAL ← 0;
DeUnderlineSelection[target, target];
targetPoint ← targetStart
← IF target.pendingDelete THEN target.start ELSE target.point;
targetEnd ← IF target.pendingDelete THEN target.end ELSE target.point;
delStart ← MIN[sourceSel.start, targetStart];
delEnd ← MAX[sourceSel.end, targetEnd];
vmD.InsertRangeInMessage[targetIndex: 0, targetMessage: mnp.deletionBuffer,
from: vmD.MessageRange[delStart, delEnd, message]];
vmD.DeleteRangeInMessage[from: [sourceSel.start, sourceSel.end, message]];
SELECT TRUE FROM
(targetStart IN [sourceSel.start .. sourceSel.end)) =>
BEGIN
IF targetEnd > sourceSel.end THEN
vmD.DeleteRangeInMessage[
[sourceSel.start, sourceSel.start + (targetEnd - sourceSel.end), message]];
targetPoint ← sourceSel.start;
overlap ← MIN[sourceSel.end, targetEnd] - targetStart;
END;
(targetEnd IN [sourceSel.start .. sourceSel.end)) =>
BEGIN
vmD.DeleteRangeInMessage[[targetStart, sourceSel.start, message]];
overlap ← targetEnd - sourceSel.start;
END;
(targetStart <= sourceSel.start AND targetEnd >= sourceSel.end) =>
BEGIN
vmD.DeleteRangeInMessage
[[targetStart, targetEnd - insertionLength, message]];
overlap ← insertionLength;
END;
(targetStart >= sourceSel.end) =>
BEGIN
vmD.DeleteRangeInMessage[
[targetStart - insertionLength, targetEnd - insertionLength, message]];
targetPoint ← targetStart - insertionLength;
END;
ENDCASE => vmD.DeleteRangeInMessage[[targetStart, targetEnd, message]];
vmD.InsertRangeInMessage[targetPoint, message,
[sourceSel.start - delStart, sourceSel.end - delStart, mnp.deletionBuffer]];
vmD.InsertRangeInMessage[0, mnp.insertionBuffer,
[delStart, delEnd - (targetEnd - targetStart - overlap), message]];
actionPoint↑ ← delStart;
commandType↑ ← replace;
target↑ ← [mnp, targetPoint + insertionLength, targetPoint + insertionLength, 0, 0, char,
FALSE];
UpdateScreen[start: delStart, nCharsDeleted: delEnd - delStart,
nCharsInserted: vmD.GetMessageSize[mnp.insertionBuffer],
mnp: mnp, select: TRUE];
target.point ← targetPoint + insertionLength;
intC.newTargetSelection ← TRUE;
GO TO UnlockSourceSel;
END;
insertionBufferLength ← vmD.GetMessageSize[mnp.insertionBuffer];
vmD.InsertRangeInMessage
[targetIndex: insertionBufferLength, targetMessage: mnp.insertionBuffer,
from: vmD.MessageRange[sourceSel.start, sourceSel.end, sourceSel.mnp.message]];
IF target.pendingDelete THEN
BEGIN
target.pendingDelete ← FALSE;
actionPoint↑ ← start ← target.start;
end ← target.end;
commandType↑ ← replace;
vmD.InsertRangeInMessage
[targetIndex: 0, targetMessage: mnp.deletionBuffer, from: [start, end, message]];
END
ELSE BEGIN
start ← end ← target.point;
IF newInsertion THEN
BEGIN
DeUnderlineSelection[target, target];
actionPoint↑ ← target.point;
commandType↑ ← insert;
END
ELSE BEGIN
IF ~modelessEditor THEN commandType↑ ← intC.pendingCommandType
ELSE SELECT commandType↑ FROM
delete => commandType↑ ← replace;
insert, replace => NULL;
ENDCASE => commandType↑ ← insert;
END;
END;
vmD.ReplaceRangeInMessage[to: [start, end, message],
from: vmD.MessageRange[insertionBufferLength,
insertionBufferLength + insertionLength, mnp.insertionBuffer]];
target↑ ← TextSelection[mnp, start, start + insertionLength, 0, 0, char, FALSE];
IF modalSecondaryInsertion THEN
{intC.pendingCommandType ← noCommand; actionPoint↑ ← start}
ELSE {target.start ← target.end; intC.newTargetSelection ← FALSE};
UpdateScreen[start: start, nCharsDeleted: end - start,
nCharsInserted: insertionLength, mnp: mnp, select: modalSecondaryInsertion];
GO TO UnlockSourceSel;
EXITS
UnlockSourceSel =>
BEGIN
IF (sourceSel.key = 0) # sourceSel.mnp.editable THEN exD.SysBug[];
IF sourceSel.key # 0 THEN
vmD.UnlockTOC[vmD.DisplayMessage[sourceSel.mnp.message].toc, sourceSel.key];
END;
END; -- for EXITS block --
END; -- of InsertSourceSelection --


TerminateInsertion: PROCEDURE [mnp: MessageTextNbrPtr] =
BEGIN
IF intC.pendingCommandType = noCommand THEN exD.SysBug[];
intC.commandType ← intC.pendingCommandType;
IF intC.pendingCommandType # replace THEN ResetDeletionBuffer[mnp];
intC.pendingCommandType ← noCommand;
target↑ ← TextSelection[mnp, intC.actionPoint, target.point, target.point, 0, char, FALSE];
DoTargetSelection[];
TurnOnDeliver[];
END; -- of TerminateInsertion --


CancelTargetSelection: PUBLIC PROCEDURE =
BEGIN
DeUnderlineSelection[target, target];
target.pendingDelete ← FALSE;
intC.newTargetSelection ← FALSE;
END; -- of CancelTargetSelection --


DoTargetSelection: PUBLIC PROCEDURE =
BEGIN
IF ~modelessEditor AND target.start = target.end THEN
target.end ← MIN[target.end + 1, vmD.GetMessageSize[target.mnp.message]];
UnderlineSelection[target, target];
intC.newTargetSelection ← (target.start # target.end);
END; -- of DoTargetSelection --


CancelSourceSelection: PUBLIC PROCEDURE [mnp: MessageTextNbrPtr] =
BEGIN
IF modelessEditor OR ~secondarySelectionEnabled↑ THEN RETURN;
secondarySelectionEnabled↑ ← FALSE;
IF source.end # 0 THEN ClearSourceSelection[];
IF intC.pendingCommandType = replace THEN ResetInsertionBuffer[mnp];
END; -- of CancelSourceSelection --


UpdateScreen: PUBLIC PROCEDURE [start: CharIndex, nCharsDeleted,
nCharsInserted: CARDINAL, mnp: MessageTextNbrPtr,
select: BOOLEAN ← TRUE] =
-- Shortcut call to screen refreshers. Updates selections and Deliver command as well.
BEGIN
RefreshFromFirstChange
[actionIndex: start, deletedChars: nCharsDeleted, insertedChars: nCharsInserted, mnp: mnp];
target.point ← start + nCharsInserted;
IF select THEN DoTargetSelection[];
TurnOnDeliver[];
END; -- of UpdateScreen --


ResetBuffers: PUBLIC PROCEDURE [mnp: MessageTextNbrPtr] =
-- Resets both mnp.insertionBufffer and mnp.deletionBuffer.
BEGIN
ResetInsertionBuffer[mnp];
ResetDeletionBuffer[mnp];
intC.commandType ← noCommand;
intC.actionPoint ← 0;
END; -- of ResetBuffers --


ResetDeletionBuffer: PUBLIC PROCEDURE [mnp: MessageTextNbrPtr] =
BEGIN
vmD.DeleteRangeInMessage
[from: vmD.MessageRange[0, vmD.GetMessageSize[mnp.deletionBuffer],
mnp.deletionBuffer]];
END; -- of ResetDeletionBuffer --


ResetInsertionBuffer: PUBLIC PROCEDURE [mnp: MessageTextNbrPtr] =
-- Resets mnp.insertionBuffer to be empty.
BEGIN
vmD.DeleteRangeInMessage
[from: vmD.MessageRange[0, vmD.GetMessageSize[mnp.insertionBuffer],
mnp.insertionBuffer]];
END; -- of ResetInsertionBuffer --


SwapMessageWithDeletionBuffer: PUBLIC PROCEDURE [mnp: MessageTextNbrPtr] =
-- Exchanges contents of mnp.message with mnp.deletionBuffer.
BEGIN
tempBuffer: vmD.ComposedMessagePtr ← mnp.deletionBuffer;
mnp.deletionBuffer ← vmD.ComposedMessage[mnp.message];
mnp.message ← tempBuffer;
END; -- of SwapMessageWithDeletionBuffer --


SwapMessageWithInsertionBuffer: PUBLIC PROCEDURE [mnp: MessageTextNbrPtr] =
-- Exchanges contents of mnp.message with mnp.insertionBuffer.
BEGIN
tempBuffer: vmD.ComposedMessagePtr ← mnp.insertionBuffer;
mnp.insertionBuffer ← vmD.ComposedMessage[mnp.message];
mnp.message ← tempBuffer;
END; -- of SwapMessageWithInsertionBuffer --


SwapInsertionWithDeletionBuffer: PUBLIC PROCEDURE [mnp: MessageTextNbrPtr] =
-- Exchanges contents of mnp.insertionBuffer with mnp.deletionBuffer.
BEGIN
tempBuffer: vmD.ComposedMessagePtr ← mnp.insertionBuffer;
mnp.insertionBuffer ← mnp.deletionBuffer;
mnp.deletionBuffer ← tempBuffer;
END; -- of SwapInsertionWithDeletionBuffer --


TurnOnDeliver: PUBLIC PROCEDURE =
BEGIN
deliverCommandHouse: HousePtr = intC.deliverCommandHouse;
intC.composedMessageEdited ← TRUE;
IF deliverCommandHouse.typeface ~= boldFace THEN
BEGIN
deliverCommandHouse.text.length ← 0;
String.AppendString[deliverCommandHouse.text, "Deliver"L];
deliverCommandHouse.typeface ← boldFace;
deliverCommandHouse.callable ← TRUE;
IF intC.deliverCommandVisible THEN TextHouseRefresher[deliverCommandHouse];
END;
END; -- of TurnOnDeliver --


END. -- of EditorMain --