-- file: IntBracketsCom.Mesa
-- edited by Brotz, April 9, 1981 2:10 PM
-- edited by Schroeder, December 2, 1980 9:59 AM
-- edited by Levin, January 27, 1981 12:20 PM

DIRECTORY
AltoFileDefs,
Ascii,
crD: FROM "CoreDefs",
csD: FROM "CoreStreamDefs",
displayCommon: FROM "DisplayCommon",
dsD: FROM "DisplayDefs",
Editor,
exD: FROM "ExceptionDefs",
inD: FROM "InteractorDefs",
Inline,
intCommon: FROM "IntCommon",
KeyDefs,
ovD: FROM "OverviewDefs",
Storage,
StreamDefs,
String,
vmD: FROM "VirtualMgrDefs";

IntBracketsCom: PROGRAM
IMPORTS crD, csD, disC: displayCommon, dsD, Editor, exD, inD, Inline, intC: intCommon,
Storage, String, vmD
EXPORTS inD =

BEGIN
OPEN inD;

-- Purpose: handles user interactions including the display, keyboard and
-- mouse. This division gathers together commands and their arguments and is
-- responsible for the display of all error messages.

-- Global variables --

currentBracketsHouse: HousePtr ← NIL;
currentBracketsX: ScreenXCoord;


ConfirmBrackets: PUBLIC PROC [hp: HousePtr, acceptWhiteSpace: BOOLEAN ← FALSE,
fileExtension: STRING ← NIL]
RETURNS [confirmed: BOOLEAN] =
-- Accepts keyboard input file name, sets hp.text to this file name, and displays
-- it in its display position. Changes area of screen sensitive to mouse to
-- reflect new brackets size. Returns Boolean value FALSE if brackets fill in
-- is aborted, TRUE if completed.
BEGIN
aborted: BOOLEAN;
dirStream: csD.StreamHandle ← NIL;
foundMatch: BOOLEAN ← FALSE;
cachePtr: crD.CacheNodePtr ← crD.fileCacheHeader;

GetNextFile: PROCEDURE [s: STRING] =
BEGIN
DVFront: TYPE = MACHINE DEPENDENT RECORD
[SELECT OVERLAID * FROM
word => [word: UNSPECIFIED],
stuff => [type: [AltoFileDefs.DEfree .. AltoFileDefs.DElast], length: [0 .. 1777B]],
ENDCASE];
dvFront: DVFront;
filename: STRING ← [AltoFileDefs.FilenameChars];

EquivPart: PROCEDURE [s: STRING, part: String.SubString] RETURNS [BOOLEAN] =
BEGIN
ssd: String.SubStringDescriptor ← [s, 0, s.length];
RETURN[String.EquivalentSubStrings[@ssd, part]];
END; -- of EquivPart --

IsMatch: PROCEDURE [file: STRING] RETURNS [BOOLEAN] =
BEGIN
ssd: String.SubStringDescriptor;
IF file.length <= fileExtension.length THEN RETURN[FALSE];
ssd ← [file, file.length - fileExtension.length, fileExtension.length];
IF EquivPart[fileExtension, @ssd] THEN
BEGIN
ssd.length ← ssd.offset; ssd.offset ← 0;
IF ~EquivPart[s, @ssd] THEN
BEGIN
s.length ← 0; String.AppendSubString[s, @ssd];
RETURN[TRUE];
END;
END;
RETURN[FALSE];
END; -- of IsMatch --

-- first cycle through the Alto Core file cache.

UNTIL cachePtr = NIL DO
IF IsMatch[cachePtr.key] THEN {cachePtr ← cachePtr.next; RETURN}
ELSE cachePtr ← cachePtr.next;
ENDLOOP;

IF dirStream = NIL THEN
dirStream ← csD.OpenFromName["SysDir"L, intC.user, byte, read, 2];
DO
position: csD.Position;
Advance: PROCEDURE =
BEGIN
csD.SetPosition[dirStream, MIN[position + (dvFront.length -1) * 2,
csD.GetLength[dirStream]]];
END; -- of Advance --
SELECT csD.ReadBlock[dirStream, @dvFront.word, 0, 2] FROM
0 => IF foundMatch THEN {csD.Reset[dirStream]; foundMatch ← FALSE; LOOP}
ELSE EXIT;
2 => NULL;
ENDCASE => exD.SysBug[];
position ← csD.GetPosition[dirStream];
SELECT dvFront.type FROM
AltoFileDefs.DEfree => Advance[];
AltoFileDefs.DEfile =>
BEGIN
csD.SetPosition[dirStream, position + (SIZE[AltoFileDefs.DV] -1) * 2];
[] ← csD.ReadBlock
[dirStream, @filename.text, 0, filename.length ← csD.Read[dirStream]];
Advance[];
IF IsMatch[filename] THEN {foundMatch ← TRUE; RETURN};
END;
ENDCASE => exD.SysBug[];
ENDLOOP;
END; -- of GetNextFile --

dsD.ChangeCursor[charArrow];
[aborted, hp.rightX] ← GetStringForBrackets
[hp: hp, leftX: hp.leftX, rightX: hp.rightX,
maxDeltaX: (IF hp.nextHouse = NIL
OR hp.nextHouse.lineNumber # hp.lineNumber
THEN inD.rightMargin ELSE hp.nextHouse.leftX) - hp.leftX,
bracketFace: boldFace, displayChars: TRUE, initialDisplayString: hp.text,
abortString: hp.text, outputString: hp.text, acceptWhiteSpace: acceptWhiteSpace,
enumerate: IF fileExtension = NIL THEN NIL ELSE GetNextFile];
IF dirStream ~= NIL THEN csD.Close[dirStream];
dsD.ChangeCursor[hourGlass];
exD.ClearExceptionsRegion[];
RETURN[~aborted]
END; -- of ConfirmBrackets --


Confirm: PUBLIC PROC [lineNumber: CARDINAL] RETURNS [confirmed: BOOLEAN] =
-- Displays confirmation message on lineNumber of exception region, waits for
-- confirmation input from user, displays exception if unexpected input
-- received.
BEGIN
char: CHARACTER;
keystream: StreamDefs.StreamHandle = intC.keystream;

IF ~disC.bitMapReady THEN RETURN[FALSE];
IF intC.autoConfirm THEN RETURN[TRUE];

SELECT (char ← ConfirmInner[lineNumber]) FROM
Ascii.DEL, Editor.cancelCode, ’N, ’n => confirmed ← FALSE;
Ascii.ESC, Ascii.SP, ’Y, ’y, Ascii.CR => confirmed ← TRUE;
ENDCASE =>
BEGIN
confirmed ← FALSE;
exD.DisplayBothExceptionLines[NIL, exD.illegalCommand, NIL, exD.pressToContinue];
DO
WHILE keystream.endof[keystream] DO IdleLoop[]; ENDLOOP;
IF (char ← keystream.get[keystream]) = Ascii.DEL OR char = Editor.cancelCode
THEN EXIT;
ENDLOOP;
END;
exD.ClearExceptionsRegion[];
END; -- of Confirm --


ConfirmInner: PUBLIC PROC [lineNumber: CARDINAL] RETURNS [char: CHARACTER] =
-- Displays confirmation message on lineNumber of exception region, waits for
-- confirmation input from user, returns character typed by user.
BEGIN
keystream: StreamDefs.StreamHandle = intC.keystream;
cursorState: {black, white} ← white;
cursorLastUpdated: CARDINAL;
oldCursor: dsD.CursorShape ← dsD.GetCursor[].shape;

-- flush keystream
keystream.reset[keystream];
SELECT lineNumber FROM
0 => NULL;
1 => exD.DisplayException[exD.confirmMessage];
ENDCASE => exD.DisplayExceptionLine[exD.confirmMessage, lineNumber];
dsD.SetCursor[questionMark];
cursorLastUpdated ← realTimeClock↑;
WHILE keystream.endof[keystream] AND MouseButton[middle, up] DO
IdleLoop[];
IF LOOPHOLE[realTimeClock↑ - cursorLastUpdated, CARDINAL] >= 9 THEN
BEGIN
IF cursorState = white THEN
{cursorState ← black; dsD.SetCursor[invertQuestionMark]}
ELSE {cursorState ← white; dsD.SetCursor[questionMark]};
cursorLastUpdated ← realTimeClock↑;
END;
ENDLOOP;
dsD.SetCursor[oldCursor];
RETURN[IF keystream.endof[keystream] THEN HoldYellow[] ELSE keystream.get[keystream]];
END; -- of ConfirmInner --


AskUserToConfirm: PUBLIC PROCEDURE [exception: exD.Exception]
RETURNS [confirmed: BOOLEAN] =
-- Displays string corresponding to exception and the confirmation request
-- message to user. Returns the user’s answer.
BEGIN
exD.DisplayException[exception];
RETURN[Confirm[2]]
END; -- of AskUserToConfirm --


GetStringForBrackets: PUBLIC PROCEDURE [hp: HousePtr, leftX, rightX: ScreenXCoord,
maxDeltaX: CARDINAL, bracketFace: dsD.FaceType, displayChars: BOOLEAN,
initialDisplayString, abortString, outputString: STRING,
acceptWhiteSpace: BOOLEAN ← FALSE, enumerate: PROC [STRING] ← NIL]
RETURNS [aborted: BOOLEAN, newRightX: ScreenXCoord] =
-- Displays initialDisplayString in brackets. Accepts keyboard input and
-- displays it appended to initialDisplayString. If abort (DEL or CANCEL),
-- displays abortString in brackets and terminates. On termination (DEL or
-- CANCEL or ESC (or DO)), puts the then displayed string in outputString.
-- Returns the new right x position.
BEGIN
state: {fullString, dots};
s: STRING;
limX, stringStartX, endOfDots: ScreenXCoord;
keystream: StreamDefs.StreamHandle = intC.keystream;
charWidth: CARDINAL;
trailer: STRING ← " }"L;
trailingWidth: CARDINAL = dsD.GetStringWidth[trailer, bracketFace];
i: CARDINAL;
firstChar: BOOLEAN ← TRUE;
savedInputAcceptor: KeyboardInputAcceptor ← inD.keyboardInputAcceptorPtr↑;
savedCommandMode: BOOLEAN ← intC.commandMode;
FinishedWithBrackets: ERROR = CODE;

BracketsInputAcceptor: KeyboardInputAcceptor =
BEGIN

TerminateBrackets: PROCEDURE [abort: BOOLEAN] =
BEGIN
IF (aborted ← abort) AND displayChars THEN
PutStringInBrackets[abortString];
IF ~aborted OR outputString # abortString THEN
BEGIN
outputString.length ← 0;
String.AppendString[outputString, IF aborted THEN abortString ELSE s];
END;
ReplaceTrailerWithBracket[];
ERROR FinishedWithBrackets;
END;

AcceptNormalChar: PROCEDURE =
BEGIN
IF firstChar THEN MakeBracketsAndStringEmpty[];
String.AppendChar[s, char ! String.StringBoundsFault => GO TO Ignore];
IF displayChars THEN
BEGIN
charWidth ← dsD.GetVisibleCharWidth[char];
SELECT state FROM
fullString =>
IF currentBracketsX + charWidth > limX THEN PutLongStringInBrackets[s]
ELSE {SlideTrailer[charWidth]; PutCharInBitMap[char]};
dots => RefreshAfterDots[s];
ENDCASE;
END;
EXITS
Ignore => NULL;
END; -- of AcceptNormalChar --

StopBlinkingCaret[];
SELECT char FROM
Editor.cancelCode, Ascii.DEL => TerminateBrackets[abort: TRUE];
Ascii.ControlW => BackWord[];
Ascii.ESC => TerminateBrackets[abort: FALSE];
Editor.nextBracketStringCode =>
BEGIN
IF enumerate # NIL THEN {enumerate[s]; PutStringInBrackets[s]; firstChar ← TRUE};
SetBracketsCaretBlinking[];
RETURN;
END;
Ascii.CR, Ascii.SP, Ascii.TAB =>
IF acceptWhiteSpace THEN
BEGIN
IF char # Ascii.SP THEN char ← Inline.BITOR[char, ovD.LineBreakValue];
AcceptNormalChar[];
END
ELSE TerminateBrackets[abort: FALSE];
Editor.shiftedSelectionFlag =>
BEGIN
IF firstChar THEN MakeBracketsAndStringEmpty[];
InsertSourceSelection[];
END;
Editor.insertDeletionCode =>
BEGIN
IF firstChar THEN MakeBracketsAndStringEmpty[];
InsertLastDeletion[];
END;
Ascii.BS, Ascii.ControlA => BackSpace[];
ENDCASE => AcceptNormalChar[];
firstChar ← FALSE;
SetBracketsCaretBlinking[];
END; -- of BracketsInputAcceptor --

PutCharInBitMap: PROCEDURE [char: CHARACTER] =
BEGIN
currentBracketsX ← dsD.PutCharInBitMap[char, currentBracketsX,
currentBracketsHouse.topY, plainFace];
END; -- of PutCharInBitMap --

PutStringInBitMap: PROCEDURE [s: STRING] =
BEGIN
FOR i: CARDINAL IN [0 .. s.length) DO
PutCharInBitMap[s[i]];
ENDLOOP;
END; -- of PutStringInBitMap --

GetVisibleStringWidth: PROCEDURE [s: STRING] RETURNS [width: CARDINAL] =
BEGIN
width ← 0;
FOR i: CARDINAL IN [0 .. s.length) DO
width ← width + dsD.GetVisibleCharWidth[s[i]];
ENDLOOP;
END; -- of GetVisibleStringWidth --

SlideTrailer: PROCEDURE [amount: INTEGER] = INLINE
BEGIN
dsD.SlideRectangleHorizontally[currentBracketsX, currentBracketsX + trailingWidth,
currentBracketsHouse.topY, currentBracketsHouse.bottomY, amount];
END; -- of SlideTrailer --

ReplaceTrailerWithBracket: PROCEDURE =
BEGIN
dsD.ClearRectangle[currentBracketsX, currentBracketsX + trailingWidth,
currentBracketsHouse.topY, currentBracketsHouse.bottomY];
newRightX ← dsD.PutCharInBitMap[’}, currentBracketsX,
currentBracketsHouse.topY, bracketFace];
END; -- of ReplaceTrailerWithBracket --

MakeBracketsAndStringEmpty: PROCEDURE =
BEGIN
IF s.length > 0 THEN
BEGIN
s.length ← 0;
SlideTrailer[stringStartX - currentBracketsX];
currentBracketsX ← stringStartX;
state ← fullString;
END;
END; -- of MakeBracketsAndStringEmpty --

ResetBrackets: PROCEDURE =
BEGIN
dsD.ClearRectangle[leftX, currentBracketsX + trailingWidth,
currentBracketsHouse.topY, currentBracketsHouse.bottomY];
currentBracketsX ← dsD.PutCharInBitMap
[’{, leftX, currentBracketsHouse.topY, bracketFace];
END; -- of ResetBrackets --

RefreshAfterDots: PROCEDURE[s: STRING] =
BEGIN
x: ScreenXCoord ← endOfDots;
FOR i ← s.length-1, i-1 DO
IF (x ← dsD.GetVisibleCharWidth[s[i]] + x) > limX THEN EXIT;
ENDLOOP;
dsD.ClearRectangle[endOfDots, currentBracketsX + trailingWidth,
currentBracketsHouse.topY, currentBracketsHouse.bottomY];
currentBracketsX ← endOfDots;
FOR i IN [i + 1 .. s.length) DO
PutCharInBitMap[s[i]];
ENDLOOP;
[] ← dsD.PutStringInBitMap[currentBracketsX, currentBracketsHouse.topY,
trailer, bracketFace];
END; -- of RefreshAfterDots --

PutLongStringInBrackets: PROCEDURE [s: STRING] =
BEGIN
ResetBrackets[];
FOR i IN [0..5] DO
PutCharInBitMap[s[i]];
ENDLOOP;
PutStringInBitMap["..."L];
endOfDots ← currentBracketsX;
state ← dots;
RefreshAfterDots[s];
END; -- of PutLongStringInBrackets --

PutShortStringInBrackets: PROCEDURE [s: STRING] = INLINE
BEGIN
ResetBrackets[];
PutStringInBitMap[s];
[] ← dsD.PutStringInBitMap[currentBracketsX, currentBracketsHouse.topY,
trailer, bracketFace];
state ← fullString;
END; -- of PutShortStringInBrackets --

PutStringInBrackets: PROCEDURE [s: STRING] =
BEGIN
IF stringStartX + GetVisibleStringWidth[s] > limX THEN PutLongStringInBrackets[s]
ELSE PutShortStringInBrackets[s];
END; -- of PutStringInBrackets --

BackSpace: PROCEDURE =
BEGIN
IF s.length > 0 THEN
BEGIN
IF intC.editorType = modal OR (KeyDefs.Keys.FL4 = up
AND KeyDefs.Keys.FR5 = up AND KeyDefs.Keys.Spare3 = up)
THEN BEGIN -- just a backspace.
char: CHARACTER ← s[s.length ← s.length - 1];
IF displayChars THEN
BEGIN
charWidth ← dsD.GetVisibleCharWidth[char];
SELECT state FROM
fullString => BEGIN
SlideTrailer[-charWidth];
currentBracketsX ← currentBracketsX - charWidth;
END;
dots =>
IF stringStartX + GetVisibleStringWidth[s] <= limX
THEN BEGIN
ResetBrackets[];
PutStringInBitMap[s];
[] ← dsD.PutStringInBitMap[currentBracketsX,
currentBracketsHouse.topY, trailer, bracketFace];
state ← fullString;
END
ELSE RefreshAfterDots[s];
ENDCASE;
END;
END
ELSE BackWord[]; -- modeless backword.
END;
END; -- of BackSpace --

BackWord: PROCEDURE =
BEGIN
-- First, back up over punctuation characters.
UNTIL s.length = 0 DO
SELECT disC.charPropertyTable[s[s.length - 1]] FROM
punctuation, white => s.length ← s.length - 1;
ENDCASE => EXIT;
ENDLOOP;
-- Then, back up over non-punctuation characters.
UNTIL s.length = 0 DO
SELECT disC.charPropertyTable[s[s.length - 1]] FROM
punctuation, white => EXIT;
ENDCASE => s.length ← s.length - 1;
ENDLOOP;
PutStringInBrackets[s];
END; -- of BackWord --

InsertSourceSelection: PROCEDURE = INLINE
BEGIN
selection: TextSelection ← intC.source;
Editor.ClearSourceSelection[];
InsertFromMessage[selection.start, selection.end, selection.mnp.message];
END; -- of InsertSourceSelection --

InsertLastDeletion: PROCEDURE = INLINE
BEGIN
message: vmD.VirtualMessagePtr = intC.cmTextNbr.deletionBuffer;
InsertFromMessage[0, vmD.GetMessageSize[message], message];
END; -- of InsertLastDeletion --

InsertFromMessage: PROCEDURE [start, end: ovD.CharIndex,
message: vmD.VirtualMessagePtr] =
BEGIN
width: CARDINAL;
string: STRING ← [maxBracketStringLength];
char: CHARACTER;
end ← MIN[end, start + s.maxlength - s.length];
FOR i: ovD.CharIndex IN [start .. end) DO
SELECT (char ← Inline.BITAND[vmD.GetMessageChar[message, i],
ovD.CharMask]) FROM
Ascii.CR, Ascii.SP, Ascii.TAB =>
IF acceptWhiteSpace THEN
BEGIN
IF char # Ascii.SP THEN char ← Inline.BITOR[char, ovD.LineBreakValue];
String.AppendChar[string, char];
END
ELSE {keystream.putback[keystream, Ascii.ESC]; EXIT};
ENDCASE => String.AppendChar[string, char];
ENDLOOP;
String.AppendString[s, string];
width ← GetVisibleStringWidth[string];
IF displayChars THEN
SELECT state FROM
fullString =>
IF currentBracketsX + width > limX THEN PutLongStringInBrackets[s]
ELSE {SlideTrailer[width]; PutStringInBitMap[string]};
dots => RefreshAfterDots[s];
ENDCASE;
END; -- of InsertFromMessage --

currentBracketsHouse ← hp;
s ← Storage.String[outputString.maxlength];
String.AppendString[s, initialDisplayString];
limX ← leftX + maxDeltaX - trailingWidth;
currentBracketsX ← rightX - trailingWidth; -- rightX may not equal leftX+maxDeltaX
stringStartX ← dsD.GetStringWidth["{"L, bracketFace] + leftX;
PutStringInBrackets[s];
keyboardInputAcceptorPtr↑ ← BracketsInputAcceptor;
intC.commandMode ← FALSE;
SetBracketsCaretBlinking[];
ScreenTracker[brackets ! FinishedWithBrackets => CONTINUE];
currentBracketsHouse ← NIL;
Storage.FreeString[s];
keyboardInputAcceptorPtr↑ ← savedInputAcceptor;
intC.commandMode ← savedCommandMode;
END; -- of GetStringForBrackets --


SetBracketsCaretBlinking: PUBLIC PROCEDURE =
-- Like StartBlinkingCaret, but works with the current caret in brackets.
BEGIN
IF currentBracketsHouse = NIL THEN ERROR;
StartBlinkingCaret[currentBracketsX,
(currentBracketsHouse.topY + currentBracketsHouse.bottomY) / 2];
END; -- of SetBracketsCaretBlinking --


HoldYellow: PRIVATE PROCEDURE RETURNS [char: CHARACTER] =
BEGIN
keystream: StreamDefs.StreamHandle = intC.keystream;
char ← Ascii.ESC;
UNTIL MouseButton[middle, up] DO
IdleLoop[];
IF ~keystream.endof[keystream] THEN char ← keystream.get[keystream];
IF MouseButton[left, down] OR MouseButton[right, down] THEN
char ← IF intC.editorType = modeless THEN Editor.cancelCode ELSE Ascii.DEL;
ENDLOOP;
END; -- of HoldYellow --


END. -- of IntBracketsCom --