-- file: EditorFind.Mesa
-- edited by Brotz, September 24, 1982 11:53 AM
-- edited by Barth, December 15, 1980 4:18 PM
-- edited by Schroeder, March 15, 1981 3:17 PM


DIRECTORY
Ascii USING [ControlA, ControlB],
dsD: FROM "DisplayDefs" USING [CharProperty, GetCharBreakProp],
Editor USING [CancelSourceSelection, CancelTargetSelection, DoTargetSelection,
MakeCharIndexVisible, RefreshToPlaceCharOnLine, ResetBuffers, TurnOnDeliver],
exD: FROM "ExceptionDefs" USING [ClearExceptionsRegion, DisplayException,
DisplayExceptionLine, DisplayExceptionString, DisplayExceptionStringOnLine,
findHintOne, findHintTwo, malformedSearchString, noBlankFound, noSubMade,
oneSubMade, SysBug, textNotFound],
inD: FROM "InteractorDefs" USING [CharIndex, CommandProcedure, ConfirmBrackets,
IndicateCommandBusy, IndicateCommandFinished, maxBracketStringLength,
MessageTextNbrPtr, SetCaretBlinking, StopBlinkingCaret, TextSelection,
TextSelectionPtr],
Inline USING [BITAND, BITOR],
intCommon USING [actionPoint, cmTextNbr, commandType, composedMessageEdited,
currentCommand, editorType, findBracketsHouse, findHouse,
pendingDeleteSetByControl, substituteHouse, substituteNewBracketsHouse,
substituteOldBracketsHouse, target],
lmD: FROM "LaurelMenuDefs" USING [ChangeEditorMenu],
ovD: FROM "OverviewDefs" USING [CharMask, LineBreakValue],
prD: FROM "ProtectionDefs" USING [FindUnprotectedSubrange],
Storage USING [Free, Node],
String USING [AppendDecimal, AppendString, LowerCase, UpperCase],
vmD: FROM "VirtualMgrDefs" USING [CharCache, CharIndex, ComposedMessage,
ComposedMessagePtr, DeleteRangeInMessage, GetMessageChar, GetMessageSize,
InsertRangeInMessage, InsertSubstringInMessage, MessageRange, PutMessageChar,
StartMessageInsertion, StopMessageInsertion, VirtualMessagePtr];

EditorFind: PROGRAM
IMPORTS dsD, Editor, exD, inD, intC: intCommon, Inline, lmD, prD, Storage, String,
vmD
EXPORTS Editor, inD =

BEGIN
OPEN Editor, inD;


FCommand: PUBLIC PROCEDURE =
BEGIN
lmD.ChangeEditorMenu[find];
IndicateCommandBusy[intC.currentCommand ← intC.findHouse];
FindCommand[intC.findHouse, FALSE];
intC.currentCommand ← NIL;
IndicateCommandFinished[intC.findHouse];
END; -- of FCommand --


FindCommand: PUBLIC CommandProcedure =
-- Starting at the current selection, searches for text in following brackets. If
-- found, selects the found text and normalizes it in the composed message
-- region.
BEGIN
mnp: MessageTextNbrPtr = intC.cmTextNbr;
target: TextSelectionPtr = @intC.target;
found: BOOLEAN;
at, atEnd: CharIndex;
targetRange: vmD.MessageRange;
IF ~mnp.haveMessage THEN RETURN;
IF ~confirmed THEN BEGIN
DisplayFindSyntaxHint[];
IF ~ConfirmBrackets[intC.findBracketsHouse, TRUE] THEN RETURN;
END;
[found, at, atEnd] ← FindOperation[intC.findBracketsHouse.text, target.end,
vmD.GetMessageSize[mnp.message], mnp.message];
targetRange ← [at, atEnd, mnp.message];
IF found THEN
BEGIN
IF mnp.protectedFieldPtr # NIL THEN
BEGIN
prD.FindUnprotectedSubrange[pfp: mnp.protectedFieldPtr, rangePtr: @targetRange,
fixStart: TRUE];
IF targetRange.end # atEnd THEN
exD.DisplayExceptionString["Selection shortened to lie within unprotected region!"L];
END;
CancelTargetSelection[];
CancelSourceSelection[mnp];
StopBlinkingCaret[];
target↑ ← TextSelection
[mnp, targetRange.start, targetRange.end, targetRange.start, 0, char,
intC.editorType = modeless AND targetRange.start < targetRange.end];
intC.pendingDeleteSetByControl ← FALSE;
DoTargetSelection[];
[] ← MakeCharIndexVisible[at, mnp];
IF intC.editorType = modeless THEN SetCaretBlinking[target.point, mnp];
END
ELSE exD.DisplayException[exD.textNotFound];
END; -- of FindCommand --


DisplayFindSyntaxHint: PROCEDURE =
BEGIN
exD.DisplayExceptionLine[exD.findHintOne, 1];
exD.DisplayExceptionLine[exD.findHintTwo, 2];
END; -- of DisplayFindSyntaxHint --


SCommand: PUBLIC PROCEDURE =
BEGIN
lmD.ChangeEditorMenu[substitute];
IndicateCommandBusy[intC.currentCommand ← intC.substituteHouse];
SubstituteCommand[intC.substituteHouse, FALSE];
intC.currentCommand ← NIL;
IndicateCommandFinished[intC.substituteHouse];
END; -- of SCommand --


SubstituteCommand: PUBLIC CommandProcedure =
-- Within the bounds of the current selection, substitutes the text in the
-- following brackets for each occurence of the text in the preceding brackets.
BEGIN
target: TextSelectionPtr = @intC.target;
originalStart, start, end, at, atEnd, patternEnd: CharIndex;
mnp: MessageTextNbrPtr ← target.mnp;
composedMessage: vmD.ComposedMessagePtr ← vmD.ComposedMessage[mnp.message];
old: STRING ← intC.substituteOldBracketsHouse.text;
new: STRING ← intC.substituteNewBracketsHouse.text;
normalNew: STRING ← [maxBracketStringLength];
found: BOOLEAN;
matchedLength, putLength: CARDINAL;
count: CARDINAL ← 0;
IF ~mnp.haveMessage THEN RETURN;
IF ~confirmed THEN
BEGIN
IF ~ConfirmBrackets[intC.substituteNewBracketsHouse, TRUE] THEN RETURN;
DisplayFindSyntaxHint[];
IF ~ConfirmBrackets[intC.substituteOldBracketsHouse, TRUE] THEN
{exD.ClearExceptionsRegion[]; RETURN};
END;
putLength ← MIN[new.length, old.length];
originalStart ← start ← target.start;
end ← target.end;
exD.ClearExceptionsRegion[];
FOR i: CARDINAL IN [0 .. new.length) DO
normalNew[i] ← Inline.BITAND[new[i], ovD.CharMask];
ENDLOOP;
DO
[found, at, atEnd, patternEnd] ← FindOperation[old, start, end, composedMessage];
IF ~found THEN EXIT;
IF count = 0 THEN
BEGIN -- save selected range for undo.
ResetBuffers[mnp];
vmD.InsertRangeInMessage[0, mnp.deletionBuffer, [start, end, composedMessage]];
intC.commandType ← replace;
intC.actionPoint ← originalStart;
END;
count ← count + 1;
matchedLength ← atEnd - at;
end ← end + new.length - matchedLength;
putLength ← MIN[new.length, matchedLength];
FOR i: CARDINAL IN [0 .. putLength) DO
vmD.PutMessageChar[composedMessage, at + i, normalNew[i]];
ENDLOOP;
SELECT new.length FROM
< matchedLength => vmD.DeleteRangeInMessage
[from: [start: at + putLength, end: atEnd, message: composedMessage]];
> matchedLength =>
BEGIN
vmD.StartMessageInsertion[composedMessage, at + putLength];
[] ← vmD.InsertSubstringInMessage[composedMessage, normalNew,
putLength, new.length - putLength];
vmD.StopMessageInsertion[composedMessage];
END;
ENDCASE;
start ← at + new.length + (patternEnd - atEnd);
ENDLOOP;
IF count > 0 THEN
BEGIN
vmD.InsertRangeInMessage[0, mnp.insertionBuffer, [target.start, end, composedMessage]];
IF intC.editorType = modeless THEN StopBlinkingCaret[];
IF count = 1 THEN exD.DisplayExceptionLine[exD.oneSubMade, 1]
ELSE BEGIN
countMessage: STRING ← [30];
String.AppendDecimal[countMessage, count];
String.AppendString[countMessage, " substitutions made."L];
exD.DisplayExceptionStringOnLine[countMessage, 1];
END;
target.pendingDelete ← FALSE;
target.end ← end;
target.mode ← char;
target.point ← end;
composedMessage.formatStart ← composedMessage.formatEnd ← 0;
RefreshToPlaceCharOnLine[originalStart, mnp.lines, mnp, TRUE];
IF intC.editorType = modeless THEN SetCaretBlinking[end, mnp];
intC.composedMessageEdited ← TRUE;
TurnOnDeliver[];
END
ELSE exD.DisplayException[exD.noSubMade];
END; -- of SubstituteCommand --


FindOperation: PUBLIC PROCEDURE [s: STRING, start, end: CharIndex,
message: vmD.VirtualMessagePtr]
RETURNS [found: BOOLEAN, at, atEnd, patternEnd: CharIndex] =
-- Searches for a match to s within [start .. end) of mnp.message. If found, returns TRUE,
-- with [at .. atEnd) the matched range,
BEGIN
anyStringPattern: CHARACTER = ovD.LineBreakValue;
oneCharPattern: CHARACTER = ovD.LineBreakValue + 1;
oneAlphaPattern: CHARACTER = ovD.LineBreakValue + 2;
oneNonAlphaPattern: CHARACTER = ovD.LineBreakValue + 3;
anyAlphaPattern: CHARACTER = ovD.LineBreakValue + 4;
anyNonAlphaPattern: CHARACTER = ovD.LineBreakValue + 5;
leftBracketPattern: CHARACTER = ovD.LineBreakValue + 6;
rightBracketPattern: CHARACTER = ovD.LineBreakValue + 7;
StackArray: TYPE = ARRAY [1 .. maxBracketStringLength] OF CARDINAL;
textPosStack, patternPosStack: POINTER TO StackArray;
patternString: STRING ← [maxBracketStringLength];
char, patternChar, firstPatChar1, firstPatChar2: CHARACTER ← 377C;
charType: dsD.CharProperty;
psIndex: CARDINAL ← 0;
beginPos, endPos, patternPos, textPos, patternAnchor, textAnchor: CARDINAL;
stackPtr: [0..maxBracketStringLength] ← 0;
get: POINTER TO vmD.CharCache ← @message.get;
leftBracketSeen, rightBracketSeen: BOOLEAN ← FALSE;
firstPatternCharIsNormal: BOOLEAN ← FALSE;

found ← FALSE;

-- start of main line code for FindOperation --
-- first, normalize search string, processing special characters.

IF s.length > maxBracketStringLength THEN exD.SysBug[];
BEGIN -- for EXITS.
i: CARDINAL;
FOR i ← 0, i + 1 UNTIL i >= s.length DO
SELECT (char ← Inline.BITAND[s[i], ovD.CharMask]) FROM
’’ =>
BEGIN
IF i + 1 < s.length THEN i ← i + 1 ELSE GO TO MalformedInput;
patternString[psIndex] ← s[i];
IF psIndex = 0 THEN {firstPatternCharIsNormal ← TRUE; firstPatChar1 ← s[i]};
END;
IN [’A .. ’Z], IN [’a .. ’z] =>
BEGIN
patternString[psIndex] ← Inline.BITOR[char, ovD.LineBreakValue];
IF psIndex = 0 THEN
BEGIN
firstPatternCharIsNormal ← TRUE;
firstPatChar1 ← String.UpperCase[char];
firstPatChar2 ← String.LowerCase[char];
END;
END;
’# => patternString[psIndex] ← oneCharPattern;
’* => patternString[psIndex] ← anyStringPattern;
’@ => patternString[psIndex] ← oneAlphaPattern;
’! => patternString[psIndex] ← oneNonAlphaPattern;
’& => patternString[psIndex] ← anyAlphaPattern;
’~ => patternString[psIndex] ← anyNonAlphaPattern;
’{ => patternString[psIndex] ← leftBracketPattern;
’} => patternString[psIndex] ← rightBracketPattern;
ENDCASE =>
BEGIN
patternString[psIndex] ← char;
IF psIndex = 0 THEN {firstPatternCharIsNormal ← TRUE; firstPatChar1 ← char};
END;
IF char = ’{ THEN
{IF leftBracketSeen THEN GO TO MalformedInput ELSE leftBracketSeen ← TRUE};
IF char = ’} THEN
{IF rightBracketSeen THEN GO TO MalformedInput ELSE rightBracketSeen ← TRUE};
psIndex ← psIndex + 1;
ENDLOOP;
FOR psIndex ← psIndex, psIndex - 1 UNTIL psIndex = 0 DO
SELECT patternString[psIndex - 1] FROM
anyStringPattern, anyAlphaPattern, anyNonAlphaPattern, leftBracketPattern,
rightBracketPattern => NULL;
ENDCASE => EXIT;
ENDLOOP;
IF (patternString.length ← psIndex) = 0 THEN GO TO MalformedInput;
EXITS
MalformedInput =>
{exD.DisplayException[exD.malformedSearchString]; RETURN};
END; -- for EXITS
-- Now search.
BEGIN -- for EXITS
textPosStack ← Storage.Node[SIZE[StackArray]];
patternPosStack ← Storage.Node[SIZE[StackArray]];

FOR at ← start, at + 1 UNTIL at = end DO
IF firstPatternCharIsNormal THEN
BEGIN
char ← Inline.BITAND[ovD.CharMask, IF at IN [get.first .. get.free)
THEN message.buffer.chars[at + get.floor - get.first]
ELSE vmD.GetMessageChar[message, at]];
IF char # firstPatChar1 AND char # firstPatChar2 THEN LOOP;
patternPos ← 1;
textPos ← at + 1;
END
ELSE {patternPos ← 0; textPos ← at};
-- save where name began matching non-* seg of pattern
patternAnchor ← 0;
beginPos ← textAnchor ← at;
DO
IF patternPos >= patternString.length THEN
BEGIN
found ← TRUE;
at ← beginPos;
atEnd ← IF rightBracketSeen THEN endPos ELSE textPos;
patternEnd ← textPos;
GO TO Return;
END;
SELECT (patternChar ← patternString[patternPos]) FROM
anyStringPattern =>
{patternAnchor ← patternPos ← patternPos + 1; textAnchor ← textPos; stackPtr ← 0};
leftBracketPattern =>
{beginPos ← textPos; patternPos ← patternPos +1};
rightBracketPattern =>
{endPos ← textPos; patternPos ← patternPos +1};
anyNonAlphaPattern, anyAlphaPattern =>
BEGIN
stackPtr ← stackPtr + 1;
textPosStack[stackPtr] ← textPos;
patternPosStack[stackPtr] ← patternPos;
patternPos ← patternPos + 1;
END;
ENDCASE =>
BEGIN
IF textPos >= end THEN GO TO Return;
IF patternChar = oneCharPattern THEN
BEGIN
IF patternPos # 0 AND patternPos = patternAnchor THEN
-- first char(s) of * segment
{patternAnchor ← patternAnchor + 1; textAnchor ← textPos + 1};
patternPos ← patternPos + 1;
textPos ← textPos + 1;
END
ELSE BEGIN
char ← Inline.BITAND
[ovD.CharMask, IF textPos IN [get.first .. get.free)
THEN message.buffer.chars[textPos + get.floor - get.first]
ELSE vmD.GetMessageChar[message, textPos]];
charType ← dsD.GetCharBreakProp[char];
IF (patternChar = oneNonAlphaPattern AND charType # alphaNumeric)
OR (patternChar = oneAlphaPattern AND charType = alphaNumeric)
OR (patternChar = char)
OR (patternChar > ovD.LineBreakValue
AND ((patternChar←Inline.BITAND[ovD.CharMask, patternChar]) IN [’A..’Z]
OR patternChar IN [’a .. ’z])
AND String.LowerCase[patternChar] = String.LowerCase[char])
THEN -- chars match -- {patternPos ← patternPos + 1; textPos ← textPos + 1}
ELSE BEGIN
WHILE stackPtr # 0 DO
charType ← dsD.GetCharBreakProp[Inline.BITAND
[ovD.CharMask, IF textPosStack[stackPtr] IN [get.first .. get.free)
THEN message.buffer.chars[textPosStack[stackPtr] + get.floor - get.first]
ELSE vmD.GetMessageChar[message, textPosStack[stackPtr]]]];
IF (patternString[patternPosStack[stackPtr]] = anyNonAlphaPattern
AND charType # alphaNumeric)
OR (patternString[patternPosStack[stackPtr]] = anyAlphaPattern
AND charType = alphaNumeric) THEN
BEGIN
patternPos ← patternPosStack[stackPtr] + 1;
textPos ← textPosStack[stackPtr] ← textPosStack[stackPtr] + 1;
EXIT
END;
stackPtr ← stackPtr - 1;
ENDLOOP;
IF stackPtr = 0 THEN -- implicit AND patternAnchor # 0 (there was a *)
BEGIN
patternPos ← patternAnchor;
textPos ← textAnchor ← textAnchor + 1;
END;
IF patternAnchor = 0 AND stackPtr = 0 THEN EXIT;
END;
END;
END;
ENDLOOP;
ENDLOOP;
GO TO Return;
EXITS
Return => {Storage.Free[textPosStack]; Storage.Free[patternPosStack]}
END; -- of EXITS --
END; -- of FindOperation --


SelectNextBlank: PUBLIC PROCEDURE
[mnp: MessageTextNbrPtr, forward: BOOLEAN ← TRUE] =
-- Implements editor NEXT command.
BEGIN
target: TextSelectionPtr = @intC.target;
nextStart, nextEnd: CharIndex;
message: vmD.VirtualMessagePtr = mnp.message;
messageLength: CharIndex = vmD.GetMessageSize[message];
targetRange: vmD.MessageRange;
IF forward THEN
BEGIN
FOR nextStart ← IF target.pendingDelete THEN target.end ELSE target.point,
nextStart + 1 UNTIL nextStart >= messageLength DO
IF vmD.GetMessageChar[message, nextStart] = Ascii.ControlA THEN EXIT;
REPEAT
FINISHED => GO TO NoBlankFound;
ENDLOOP;
FOR nextEnd ← nextStart + 1, nextEnd + 1 UNTIL nextEnd >= messageLength DO
IF vmD.GetMessageChar[message, nextEnd] = Ascii.ControlB THEN EXIT;
REPEAT
FINISHED => GO TO NoBlankFound;
ENDLOOP;
END
ELSE BEGIN
nextEnd ← IF target.pendingDelete THEN target.start ELSE target.point;
UNTIL nextEnd = 0 DO
nextEnd ← nextEnd - 1;
IF vmD.GetMessageChar[message, nextEnd] = Ascii.ControlB THEN EXIT;
REPEAT
FINISHED => GO TO NoBlankFound;
ENDLOOP;
FOR nextStart ← nextEnd, nextStart - 1 UNTIL nextStart = 0 DO
IF vmD.GetMessageChar[message, nextStart - 1] = Ascii.ControlA THEN
{nextStart ← nextStart - 1; EXIT};
REPEAT
FINISHED => GO TO NoBlankFound;
ENDLOOP;
END;
targetRange ← [nextStart, nextEnd + 1, message];
IF mnp.protectedFieldPtr # NIL THEN
BEGIN
prD.FindUnprotectedSubrange[pfp: mnp.protectedFieldPtr, rangePtr: @targetRange,
fixStart: FALSE];
IF targetRange.start # nextStart THEN exD.DisplayExceptionString
["Placeholders not edited properly. Next item would have crossed a protected field!"L];
END;
CancelTargetSelection[];
target↑ ← TextSelection
[mnp, targetRange.start, targetRange.end, targetRange.start, 0, word, TRUE];
intC.pendingDeleteSetByControl ← FALSE;
DoTargetSelection[];
[] ← MakeCharIndexVisible[nextStart, mnp];
EXITS
NoBlankFound => exD.DisplayException[exD.noBlankFound];
END; -- of SelectNextBlank --


SelectEverything: PUBLIC PROCEDURE [mnp: MessageTextNbrPtr] =
-- Implements editor E command.
BEGIN
IF mnp.protectedFieldPtr # NIL THEN
BEGIN
exD.DisplayExceptionString["Protected fields exist. Cannot select entire message!"L];
RETURN;
END;
CancelTargetSelection[];
intC.target ← TextSelection
[mnp, 0, vmD.GetMessageSize[mnp.message], 0, 0, char, intC.editorType = modeless];
intC.pendingDeleteSetByControl ← FALSE;
DoTargetSelection[];
END; -- of SelectEverything --


END. -- of EditorFind --