-- file: IntTrackEdit.Mesa
-- edited by Brotz, October 11, 1982 5:42 PM
-- edited by McCreight, September 16, 1982 9:03 AM

DIRECTORY
Ascii USING [CR],
dsD: FROM "DisplayDefs" USING [ChangeCursor, CharProperty, GetCharBreakProp,
GetCharRightX, GetCursor, ScreenXCoord, ScreenYCoord],
Editor USING [ControlKey, ControlTap, DeUnderline, DeUnderlineSelection,
MapCharIndexInLineToLeftX, MapCharIndexToLine, MapCursorToCharIndex,
MonitorTaps, ResetInsertionBuffer, ResetTaps, ShiftKey, Underline, UnderlineSelection,
UnderlineType],
exD: FROM "ExceptionDefs" USING [SysBug],
inD: FROM "InteractorDefs" USING [AcceptKeyboardInput, ButtonType, CaretIsBlinking,
CharIndex, cursorX, cursorY, IdleLoop, leftMargin, lineBarLeftX, LinePtr,
MessageTextNbrPtr, MouseButton, realTimeClock, ScreenXCoord, ScreenYCoord,
ScrollDownMessageNbr, ScrollUpMessageNbr, SelectionMode, SetCaretBlinking,
StopBlinkingCaret, TextSelection, TextSelectionPtr, TrackerType],
intCommon USING [bluePendingDelete, cmTextNbr, commandMode, continuousScrollDelay,
continuousScrollTimeOut, controlRedEnabled, currentSelection, editorType,
multiClickTimeOut, newTargetSelection, pendingDeleteSetByControl,
secondarySelectionEnabled, source, target],
ovD: FROM "OverviewDefs" USING [LineBreakValue],
prD: FROM "ProtectionDefs" USING [FindUnprotectedSubrange],
vmD: FROM "VirtualMgrDefs" USING [CharIndex, DisplayMessagePtr, GetMessageChar,
GetMessageSize, MessageRange, UnlockTOC, VirtualMessagePtr, WaitForLock];

IntTrackEdit: PROGRAM
IMPORTS dsD, Editor, exD, inD, intC: intCommon, prD, vmD
EXPORTS inD =

BEGIN
OPEN inD, Editor;

-- Editor Department of the Interactor Division
--This module does tracking specifically for the Editor

-- 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).

clickDown, clickUp: CARDINAL ← realTimeClock↑ - intC.multiClickTimeOut;

button: ButtonType;

multiClick: BOOLEAN;
modelessEditor: BOOLEAN = (intC.editorType = modeless);


TextNbrTracker: PUBLIC PROC [mnp: MessageTextNbrPtr, trackerType: TrackerType] =
-- Tracks cursor within a message text area. Monitors mouse buttons for
-- selection. Calls on TextScrollBarTracker and Selector to track the cursor
-- within their respective domains.
BEGIN
x: ScreenXCoord;
y: ScreenYCoord;
xOffset, yOffset: INTEGER;
cursorState: {scroll, line, char} ← char;

dsD.ChangeCursor[charArrow];
DO
[ , xOffset, yOffset] ← dsD.GetCursor[];
x ← cursorX↑ + xOffset;
y ← cursorY↑ + yOffset;
SELECT TRUE FROM
y ~IN [mnp.topY .. mnp.bottomY) => RETURN;
~mnp.haveMessage => NULL;
(x IN [0 .. inD.lineBarLeftX)) =>
BEGIN
TextScrollBarTracker[mnp: mnp, trackerType: trackerType];
cursorState ← scroll;
END;
(x IN [inD.lineBarLeftX .. inD.leftMargin) AND cursorState # line) =>
{dsD.ChangeCursor[lineArrow]; cursorState ← line};
(x >= inD.leftMargin AND cursorState # char) =>
{dsD.ChangeCursor[charArrow]; cursorState ← char};
MouseButton[any, down] =>
{clickDown ← realTimeClock↑; Selector[mnp, trackerType]};
ENDCASE;
IdleLoop[];
AcceptKeyboardInput[];
ENDLOOP;
END; -- of TextNbrTracker --


TextScrollBarTracker: PROCEDURE [mnp: MessageTextNbrPtr, trackerType: TrackerType] =
-- Watch cursor and mouse buttons while in the ScrollBar subneighborhood.
BEGIN
state, newState: {neutral, up, down, continuousUp, continuousDown} ← neutral;
x: ScreenXCoord;
y: ScreenYCoord;
xOffset, yOffset: INTEGER;
startTime: CARDINAL;
isCM: BOOLEAN = mnp.editable;
dm: vmD.DisplayMessagePtr = IF isCM THEN NIL ELSE LOOPHOLE[mnp.message];
key: CARDINAL ← 0;
holdLock: BOOLEAN ← FALSE;

DoScrollUp: PROCEDURE [y: ScreenYCoord] =
BEGIN
dealWithCaret: BOOLEAN = (isCM AND CaretIsBlinking[] AND trackerType = normal);
IF dealWithCaret THEN StopBlinkingCaret[];
ScrollUpMessageNbr[y, mnp];
IF dealWithCaret THEN SetCaretBlinking[intC.target.point, mnp];
END; -- of DoScrollUp --

DoScrollDown: PROCEDURE [y: ScreenYCoord] =
BEGIN
dealWithCaret: BOOLEAN = (isCM AND CaretIsBlinking[] AND trackerType = normal);
IF dealWithCaret THEN StopBlinkingCaret[];
ScrollDownMessageNbr[y, mnp];
IF dealWithCaret THEN SetCaretBlinking[intC.target.point, mnp];
END; -- of DoScrollDown --

dsD.ChangeCursor[scroll];
[ , xOffset, yOffset] ← dsD.GetCursor[];
DO
x ← cursorX↑ + xOffset;
y ← cursorY↑ + yOffset;
IF x >= inD.lineBarLeftX OR y ~IN [mnp.topY .. mnp.bottomY) THEN
{IF holdLock THEN vmD.UnlockTOC[dm.toc, key]; RETURN};
newState ← SELECT TRUE FROM
MouseButton[left, down] => IF state = continuousUp THEN state ELSE up,
MouseButton[right, down] => IF state = continuousDown THEN state ELSE down,
ENDCASE => neutral;
IF ~isCM AND state # neutral AND ~holdLock AND intC.source.key = 0 THEN
{key ← vmD.WaitForLock[dm.toc]; holdLock ← TRUE; LOOP};
SELECT state FROM
up => IF newState = neutral THEN DoScrollUp[y];
down => IF newState = neutral THEN DoScrollDown[y];
continuousUp =>
IF inD.realTimeClock↑ - startTime >= intC.continuousScrollDelay THEN
{startTime ← inD.realTimeClock↑; DoScrollUp[mnp.topY]};
continuousDown =>
IF inD.realTimeClock↑ - startTime >= intC.continuousScrollDelay THEN
{startTime ← inD.realTimeClock↑; DoScrollDown[mnp.topY]};
ENDCASE;
SELECT TRUE FROM
(state = newState AND (state = up OR state = down)) =>
IF intC.continuousScrollTimeOut # 0
AND inD.realTimeClock↑ - startTime >= intC.continuousScrollTimeOut THEN
BEGIN
state ← IF state = up THEN continuousUp ELSE continuousDown;
startTime ← inD.realTimeClock↑ - intC.continuousScrollDelay;
END;
(newState # state) => BEGIN
IF (state ← newState) # neutral THEN startTime ← inD.realTimeClock↑;
dsD.ChangeCursor
[SELECT state FROM up => scrollUp, down => scrollDown, ENDCASE => scroll];
END;
ENDCASE;
IF holdLock THEN {vmD.UnlockTOC[dm.toc, key]; holdLock ← FALSE};
IdleLoop[];
AcceptKeyboardInput[];
ENDLOOP;
END; -- of TextScrollBarTracker --


Selector: PROCEDURE [mnp: MessageTextNbrPtr, trackerType: TrackerType] =
-- Track cursor as long as initial down mouse button remains down. Scrolling is
-- not allowed while selection is still in progress; if cursor moves out of text
-- yard then selection reverts back to state at entry to Selector. UnderlineType
-- describes form of underline to be displayed on screen. Selection may be
-- made in character mode or word mode; treatment of these modes is hidden
-- behind MapXToRange routines.
BEGIN
x: ScreenXCoord;
y: ScreenYCoord;
xOffset, yOffset: INTEGER;
selPtr: TextSelectionPtr;
target: TextSelectionPtr = @intC.target;
source: TextSelectionPtr = @intC.source;
underline: UnderlineType;
secondarySelectionEnabled: POINTER TO BOOLEAN
← @intC.secondarySelectionEnabled;
commandMode: POINTER TO BOOLEAN ← @intC.commandMode;
modalShiftedSelection: BOOLEAN ← FALSE;
shiftDown: BOOLEAN = ShiftKey[down];
pendingDelete: BOOLEAN =
ControlKey[down] AND modelessEditor AND mnp.editable;

[ , xOffset, yOffset] ← dsD.GetCursor[];
x ← cursorX↑ + xOffset;
y ← cursorY↑ + yOffset;
SELECT TRUE FROM
y ~IN [mnp.topY .. mnp.bottomY) => RETURN;
(mnp.editable AND ~shiftDown AND commandMode↑) =>
{selPtr ← target; underline ← target};
((modelessEditor AND shiftDown)
OR (modalShiftedSelection ← (shiftDown AND ~commandMode↑))
OR secondarySelectionEnabled↑) =>
{selPtr ← source; underline ← source};
ENDCASE => RETURN;
Editor.ResetTaps[];
SELECT TRUE FROM
MouseButton[left, down] =>
BEGIN
multiClick ←
(button = left AND clickDown - clickUp < intC.multiClickTimeOut);
button ← left;
StartSelector[selPtr, mnp, underline,
pendingDelete AND intC.controlRedEnabled, trackerType,
IF x IN [inD.lineBarLeftX .. inD.leftMargin) THEN line ELSE char];
END;
MouseButton[middle, down] =>
BEGIN
multiClick ←
(button = middle AND clickDown - clickUp < intC.multiClickTimeOut);
button ← middle;
StartSelector[selPtr, mnp, underline, pendingDelete, trackerType,
IF x IN [inD.lineBarLeftX .. inD.leftMargin) THEN paragraph ELSE word];
END;
MouseButton[right, down] =>
BEGIN
IF (selPtr.end = selPtr.start AND underline = source) OR selPtr.mnp # mnp
THEN RETURN;
multiClick ←
(button = right AND clickDown - clickUp < intC.multiClickTimeOut);
button ← right;
ExtendSelector[selPtr, mnp, underline, pendingDelete, trackerType];
END;
ENDCASE;
IF modalShiftedSelection AND selPtr.start # selPtr.end THEN
BEGIN
IF secondarySelectionEnabled↑ THEN ResetInsertionBuffer[intC.cmTextNbr];
secondarySelectionEnabled↑ ← FALSE;
END;
END; -- of Selector --


StartSelector: PROCEDURE [selPtr: TextSelectionPtr, mnp: MessageTextNbrPtr,
underline: UnderlineType, pendingDelete: BOOLEAN, tracker: TrackerType,
mode: SelectionMode] =
-- Track cursor as long as initial down (left or middle) mouse button remains
-- down. Scrolling is not allowed while selection is still in progress; if cursor
-- moves out of text yard then selection reverts back to state at entry.
-- UnderlineType describes form of underline to be displayed on screen.
-- Selection may be made in character mode or word mode; treatment of these
-- modes is hidden behind MapXToRange routines.
BEGIN
x: ScreenXCoord;
y: ScreenYCoord;
xOffset, yOffset: INTEGER;
lastRange, thisRange: vmD.MessageRange;
lastCaretIndex, thisCaretIndex, charIndex: CharIndex;
selectMode: SelectionMode;
modelessTargetSelection: BOOLEAN ← (modelessEditor AND underline = target);
normalModelessTargetSelection: BOOLEAN ←
(modelessTargetSelection AND tracker = normal);
dm: vmD.DisplayMessagePtr = IF mnp.editable THEN NIL ELSE LOOPHOLE[mnp.message];
key: CARDINAL ← 0;

StartGuaranteeUnprotected: PROC [rangePtr: POINTER TO vmD.MessageRange] =
BEGIN
IF underline = target THEN
prD.FindUnprotectedSubrange[pfp: mnp.protectedFieldPtr, rangePtr: rangePtr,
fixStart: TRUE];
END; -- StartGuaranteeUnprotected --

[ , xOffset, yOffset] ← dsD.GetCursor[];
x ← cursorX↑ + xOffset;
y ← cursorY↑ + yOffset;
IF y ~IN [mnp.topY .. mnp.bottomY)
OR ((mode = line OR mode = paragraph)
AND x ~IN [inD.lineBarLeftX .. inD.leftMargin))
OR ((mode = char OR mode = word) AND x < inD.leftMargin)
THEN RETURN;
charIndex ← MapCursorToCharIndex[x, y, mnp];
selectMode ← IF multiClick AND charIndex IN [selPtr.start .. selPtr.end) THEN
(SELECT selPtr.mode FROM
char => word, word => line, line => paragraph, ENDCASE => everything)
ELSE mode;
IF dm # NIL AND selPtr.key = 0 THEN key ← vmD.WaitForLock[dm.toc];
lastRange ← MapCharIndexToRange[charIndex, mnp.message, selectMode];
lastCaretIndex ← FindCaretIndex[lastRange.start, lastRange.end, x, y, mnp];
IF normalModelessTargetSelection THEN StopBlinkingCaret[];
DeUnderlineSelection[selPtr, underline];
StartGuaranteeUnprotected[@lastRange];
Underline[lastRange.start, lastRange.end, underline, pendingDelete, mnp];
IF normalModelessTargetSelection THEN SetCaretBlinking[lastCaretIndex, mnp];
DO -- track start selection point
x ← cursorX↑ + xOffset;
y ← cursorY↑ + yOffset;
IF y ~IN [mnp.topY .. mnp.bottomY)
OR ((mode = line OR mode = paragraph)
AND x ~IN [inD.lineBarLeftX .. inD.leftMargin))
OR ((mode = char OR mode = word) AND x < inD.leftMargin) THEN
BEGIN
IF normalModelessTargetSelection THEN StopBlinkingCaret[];
DeUnderline[lastRange.start, lastRange.end, underline, pendingDelete, mnp];
UnderlineSelection[selPtr, underline];
IF normalModelessTargetSelection THEN SetCaretBlinking[selPtr.point, mnp];
IF key # 0 THEN vmD.UnlockTOC[dm.toc, key];
RETURN;
END;
IF ControlTap[] AND mnp.editable THEN
BEGIN
DeUnderline[lastRange.start, lastRange.end, underline, pendingDelete, mnp];
pendingDelete ← ~pendingDelete;
Underline[lastRange.start, lastRange.end, underline, pendingDelete, mnp];
END;
x ← MAX[x, inD.leftMargin];
thisRange ← MapCharIndexToRange
[MapCursorToCharIndex[x, y, mnp], mnp.message, selectMode];
StartGuaranteeUnprotected[@thisRange];
IF modelessTargetSelection THEN
thisCaretIndex ← FindCaretIndex[thisRange.start, thisRange.end, x, y, mnp];
IF thisRange # lastRange
OR (modelessTargetSelection AND thisCaretIndex # lastCaretIndex) THEN
BEGIN
IF normalModelessTargetSelection THEN StopBlinkingCaret[];
DeUnderline[lastRange.start, lastRange.end, underline, pendingDelete, mnp];
Underline[thisRange.start, thisRange.end, underline, pendingDelete, mnp];
lastRange ← thisRange;
IF normalModelessTargetSelection THEN
SetCaretBlinking[thisCaretIndex, mnp];
lastCaretIndex ← thisCaretIndex;
END;
IF MouseButton[button, up] THEN
BEGIN
clickUp ← realTimeClock↑;
IF selPtr.key # 0 THEN
BEGIN
IF mnp = selPtr.mnp THEN key ← selPtr.key
ELSE BEGIN
message: vmD.DisplayMessagePtr = LOOPHOLE[selPtr.mnp.message];
vmD.UnlockTOC[message.toc, selPtr.key];
END;
END;
selPtr↑ ← TextSelection
[mnp, lastRange.start, lastRange.end, lastCaretIndex, key, selectMode, pendingDelete];
IF underline = target THEN intC.newTargetSelection ← TRUE;
intC.currentSelection ← IF underline = source THEN source ELSE target;
intC.pendingDeleteSetByControl ← pendingDelete;
RETURN;
END;
MonitorTaps[];
IdleLoop[];
ENDLOOP; -- track start selection point
END; -- of StartSelector --


ExtendSelector: PROCEDURE [selPtr: TextSelectionPtr, mnp: MessageTextNbrPtr,
underline: UnderlineType, pendingDelete: BOOLEAN, tracker: TrackerType] =
-- Track cursor as long as right mouse button remains down. Scrolling is not
-- allowed while selection is still in progress; if cursor moves out of text yard
-- then selection reverts back to state at entry. UnderlineType describes form
-- of underline to be displayed on screen. Selection may be made in character
-- mode or word mode; treatment of these modes is hidden behind
-- MapXToRange routines.
BEGIN
firstX, x: ScreenXCoord;
y: ScreenYCoord;
xOffset, yOffset: INTEGER;
lastRange, thisRange, unprotRange: vmD.MessageRange;
fixedEnd, floatingEnd, minEnd, maxEnd, thisCaretIndex, charIndex: CharIndex;
mode: SelectionMode ← selPtr.mode;
selectMode: SelectionMode;
modelessTargetSelection: BOOLEAN ← (modelessEditor AND underline = target);
normalModelessTargetSelection: BOOLEAN ←
(modelessTargetSelection AND tracker = normal);
controlKeyUsed: BOOLEAN ← pendingDelete;

ExtendGuaranteeUnprotected: PROCEDURE [rangePtr: POINTER TO vmD.MessageRange,
fixStart: BOOLEAN ← TRUE] =
BEGIN
IF underline = target THEN
prD.FindUnprotectedSubrange[pfp: mnp.protectedFieldPtr, rangePtr: rangePtr,
fixStart: fixStart];
END; -- ExtendGuaranteeUnprotected --

[ , xOffset, yOffset] ← dsD.GetCursor[];
firstX ← cursorX↑ + xOffset;
y ← cursorY↑ + yOffset;
IF y ~IN [mnp.topY .. mnp.bottomY) THEN RETURN;
selectMode ← IF firstX IN [inD.lineBarLeftX .. inD.leftMargin) THEN
IF mode = paragraph THEN paragraph ELSE line
ELSE mode;
x ← MAX[firstX, inD.leftMargin];
charIndex ← MapCursorToCharIndex[x, y, mnp];
IF multiClick AND charIndex IN [selPtr.start .. selPtr.end) THEN
IF firstX IN [inD.lineBarLeftX .. inD.leftMargin) THEN
{selectMode ← IF selectMode = everything THEN paragraph ELSE line}
ELSE selectMode ← SELECT selectMode FROM
everything => paragraph, paragraph => line, line => word, ENDCASE => char;
IF charIndex < selPtr.start THEN
{floatingEnd ← selPtr.start; fixedEnd ← selPtr.end}
ELSE {fixedEnd ← selPtr.start; floatingEnd ← selPtr.end};
minEnd ← selPtr.start;
maxEnd ← selPtr.end;
thisRange ← MapCharIndexToRange[charIndex, mnp.message, selectMode];
ExtendGuaranteeUnprotected[@thisRange];
pendingDelete ←
pendingDelete OR (modelessTargetSelection AND intC.bluePendingDelete);
IF pendingDelete # selPtr.pendingDelete THEN
BEGIN
DeUnderlineSelection[selPtr, underline];
Underline[selPtr.start, selPtr.end, underline, pendingDelete, mnp];
END;
thisCaretIndex ← FindCaretIndex[thisRange.start, thisRange.end, x, y, mnp];
lastRange ← vmD.MessageRange[0, 0, mnp.message];

DO -- extend select track loop
-- invariant: floatingEnd to fixedEnd interval is underlined at this point.
x ← cursorX↑ + xOffset;
y ← cursorY↑ + yOffset;
IF y ~IN [mnp.topY .. mnp.bottomY) OR x < inD.lineBarLeftX
OR ((firstX IN [inD.lineBarLeftX .. inD.leftMargin))
# (x IN [inD.lineBarLeftX .. inD.leftMargin))) THEN
BEGIN
IF normalModelessTargetSelection THEN StopBlinkingCaret[];
DeUnderline[minEnd, maxEnd, underline, pendingDelete, mnp];
UnderlineSelection[selPtr, underline];
IF normalModelessTargetSelection THEN
SetCaretBlinking[intC.target.point, mnp];
RETURN;
END;
IF ControlTap[] AND mnp.editable THEN
BEGIN
DeUnderline[minEnd, maxEnd, underline, pendingDelete, mnp];
pendingDelete ← ~pendingDelete;
Underline[minEnd, maxEnd, underline, pendingDelete, mnp];
controlKeyUsed ← TRUE;
END;
x ← MAX[x, inD.leftMargin];
thisRange ← MapCharIndexToRange
[MapCursorToCharIndex[x, y, mnp], mnp.message, selectMode];
IF thisRange # lastRange THEN BEGIN
IF normalModelessTargetSelection THEN StopBlinkingCaret[];
lastRange ← thisRange;
SELECT TRUE FROM
(fixedEnd <= thisRange.start AND thisRange.end <= floatingEnd) =>
BEGIN
-- Inside the old interval. Shrink from high end.
DeUnderline[thisRange.end, floatingEnd, underline, pendingDelete, mnp];
floatingEnd ← thisRange.end;
END;
(floatingEnd <= thisRange.start AND thisRange.start < fixedEnd) =>
BEGIN
-- Inside old interval. Shrink from low end.
DeUnderline[floatingEnd, thisRange.start, underline, pendingDelete, mnp];
floatingEnd ← thisRange.start;
END;
(fixedEnd <= floatingEnd AND floatingEnd < thisRange.end) =>
BEGIN
-- Extend on high end.
unprotRange ← vmD.MessageRange[start: floatingEnd, end: thisRange.end,
message: mnp.message];
ExtendGuaranteeUnprotected[rangePtr: @unprotRange, fixStart: TRUE];
Underline[floatingEnd, unprotRange.end, underline, pendingDelete, mnp];
floatingEnd ← unprotRange.end;
END;
(thisRange.start >= fixedEnd AND fixedEnd > floatingEnd) =>
BEGIN
-- Crossover to high side of considered range and extend on high end.
DeUnderline[floatingEnd, fixedEnd, underline, pendingDelete, mnp];
fixedEnd ← selPtr.start;
unprotRange ← vmD.MessageRange[start: selPtr.end, end: thisRange.end,
message: mnp.message];
ExtendGuaranteeUnprotected[rangePtr: @unprotRange, fixStart: TRUE];
floatingEnd ← unprotRange.end;
Underline[fixedEnd, floatingEnd, underline, pendingDelete, mnp];
END;
(thisRange.start <= floatingEnd AND floatingEnd <= fixedEnd) =>
BEGIN
-- Extend on low end.
unprotRange ← vmD.MessageRange[start: thisRange.start, end: floatingEnd,
message: mnp.message];
ExtendGuaranteeUnprotected[rangePtr: @unprotRange, fixStart: FALSE];
Underline[unprotRange.start, floatingEnd, underline, pendingDelete,
mnp];
floatingEnd ← unprotRange.start;
END;
(thisRange.start <= fixedEnd AND fixedEnd < floatingEnd) =>
BEGIN
-- Crossover to low side of considered range and extend on low end.
DeUnderline[fixedEnd, floatingEnd, underline, pendingDelete, mnp];
fixedEnd ← selPtr.end;
unprotRange ← vmD.MessageRange[start: thisRange.start, end: selPtr.start,
message: mnp.message];
ExtendGuaranteeUnprotected[rangePtr: @unprotRange, fixStart: FALSE];
floatingEnd ← unprotRange.start;
Underline[floatingEnd, fixedEnd, underline, pendingDelete, mnp];
END;
ENDCASE => exD.SysBug[];
minEnd ← MIN[floatingEnd, fixedEnd];
maxEnd ← MAX[floatingEnd, fixedEnd];
thisCaretIndex ← FindCaretIndex[minEnd, maxEnd, x, y, mnp];
IF normalModelessTargetSelection THEN
SetCaretBlinking[thisCaretIndex, mnp];
END;
IF MouseButton[right, up] THEN
BEGIN
key: CARDINAL ← selPtr.key;
clickUp ← realTimeClock↑;
selPtr↑ ← TextSelection
[mnp, minEnd, maxEnd, thisCaretIndex, key, selectMode, pendingDelete];
IF underline = target THEN intC.newTargetSelection ← TRUE;
intC.pendingDeleteSetByControl ← pendingDelete AND controlKeyUsed;
RETURN;
END;
MonitorTaps[];
IdleLoop[];
ENDLOOP; -- extend select track loop
END; -- of ExtendSelector --


MapCharIndexToRange: PROCEDURE [index: CharIndex,
message: vmD.VirtualMessagePtr, mode: SelectionMode]
RETURNS [range: vmD.MessageRange] =
-- Returns the half open interval according to mode in which index lies.
-- A word is any consecutive string of white space characters or of non white
-- space characters.
BEGIN
charProp: dsD.CharProperty;
i: CharIndex;
char: CHARACTER;
messageLength: CharIndex = vmD.GetMessageSize[message];

IF index >= messageLength THEN
RETURN[vmD.MessageRange[messageLength, messageLength, message]];
range ← vmD.MessageRange[index, index + 1, message];
SELECT mode FROM
char => NULL;
word =>
BEGIN
charProp ← dsD.GetCharBreakProp[vmD.GetMessageChar[message, index]];
IF charProp = punctuation THEN RETURN;
FOR i DECREASING IN [0 .. index)
UNTIL charProp # dsD.GetCharBreakProp[char ← vmD.GetMessageChar[message, i]]
OR (char = Ascii.CR) DO
range.start ← i;
ENDLOOP;
FOR i IN [index .. messageLength)
UNTIL charProp # dsD.GetCharBreakProp[char ← vmD.GetMessageChar[message, i]]
DO
range.end ← i + 1;
IF (char = Ascii.CR) THEN RETURN;
ENDLOOP;
END;
line =>
BEGIN
FOR i DECREASING IN [0 .. index)
UNTIL ((char ← vmD.GetMessageChar[message, i]) = Ascii.CR)
OR (char >= ovD.LineBreakValue) DO
range.start ← i;
ENDLOOP;
FOR i IN [index .. messageLength) DO
char ← vmD.GetMessageChar[message, i];
range.end ← i + 1;
IF (char = Ascii.CR) OR (char >= ovD.LineBreakValue) THEN RETURN;
ENDLOOP;
END;
paragraph =>
BEGIN
FOR i DECREASING IN [0 .. index)
UNTIL (vmD.GetMessageChar[message, i] = Ascii.CR) AND
(i > 0) AND (vmD.GetMessageChar[message, i - 1] = Ascii.CR) DO
range.start ← i;
ENDLOOP;
FOR i IN [index .. messageLength) DO
range.end ← i + 1;
IF (vmD.GetMessageChar[message, i] = Ascii.CR) AND
(i > 0) AND (vmD.GetMessageChar[message, i - 1] = Ascii.CR)
THEN RETURN;
ENDLOOP;
END;
everything => {range.start ← 0; range.end ← messageLength};
ENDCASE => exD.SysBug[];
END; -- of MapCharIndexToRange --


FindCaretIndex: PROCEDURE [start, end: CharIndex, x: ScreenXCoord,
y: ScreenYCoord, mnp: MessageTextNbrPtr]
RETURNS [caretIndex: CharIndex] =
BEGIN
message: vmD.VirtualMessagePtr = mnp.message;
charIndex: CharIndex ← MapCursorToCharIndex[x, y, mnp];
charLine: LinePtr;
afterFlag, atEOL: BOOLEAN;
char: CHARACTER;
leftCharX: ScreenXCoord;
charWidth: CARDINAL;
length: CARDINAL = end - start;

-- IF charIndex = 0 THEN RETURN[0];
IF charIndex >= vmD.GetMessageSize[message] THEN RETURN[end];
IF length = 0 THEN RETURN[start];
charLine ← MapCharIndexToLine[charIndex, mnp];
IF charLine = NIL THEN exD.SysBug[];
leftCharX ← MapCharIndexInLineToLeftX[charIndex, charLine, message];
charWidth ← dsD.GetCharRightX[vmD.GetMessageChar[message, charIndex], leftCharX] - leftCharX;
afterFlag ← (length MOD 2 = 0) OR (x >= leftCharX + charWidth/2);
char ← vmD.GetMessageChar[message, end-1];
atEOL ← (char = Ascii.CR OR char >= ovD.LineBreakValue);
caretIndex ← (SELECT (start + length / 2) FROM
= charIndex => IF afterFlag THEN end ELSE start,
> charIndex => start,
< charIndex => end,
ENDCASE => ERROR);
-- Now backup the caret one character if it’s at the end of the line.
IF atEOL AND caretIndex = end THEN caretIndex ← end - 1;
END; -- of FindCaretIndex --


END. -- of IntTrackEdit --