TEditInputEventsImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) June 17, 1985 2:28:28 pm PDT
Doug Wyatt, March 3, 1985 6:18:34 pm PST
Michael Plass, March 29, 1985 5:48:59 pm PST
DIRECTORY
CedarProcess USING [SetPriority],
InputFocus USING [GetInputFocus, SetInputFocus],
List USING [Reverse],
MessageWindow USING [Append, Blink],
MonitoredQueue USING [Add, Create, MQ, Remove],
Process USING [Detach],
TEditDocument USING [GetViewerForRoot, Selection, SelectionId, SelectionRec, TEditDocumentData, RecordViewerForRoot],
TEditHistory USING [],
TEditInput USING [CheckSelection, CommandProc, InterpInput, interpreterNesting, Register],
TEditInputOps USING [CallWithLocks],
TEditLocks USING [Lock, LockOrder, Unlock],
TEditSelection USING [Copy, Deselect, LockBothSelections, MakeSelection, pSel, UnlockBothSelections],
TextNode USING [Location, Ref],
UndoEvent USING [Change, Create, Empty, Ref, Reset, SubEvent, Undo],
UserProfile USING [Number],
ViewerClasses USING [Viewer];
TEditInputEventsImpl:
CEDAR
MONITOR
IMPORTS CedarProcess, InputFocus, List, MessageWindow, MonitoredQueue, Process, TEditDocument, TEditInput, TEditInputOps, TEditLocks, TEditSelection, UndoEvent, UserProfile
EXPORTS TEditInput, TEditHistory
= BEGIN
Global variables
currentEvent: PUBLIC UndoEvent.Ref;
editEvent: EditEvent; -- the current edit event
eventNumber: INT ← 0; -- event number for current editEvent
EditEvent: TYPE = REF EditEventRec;
EditEventRec:
TYPE =
RECORD [
prev, next: EditEvent, -- links to adjacent events in edit history
undo: UndoEvent.Ref, -- stuff to undo for this event
repeatList: LIST OF REF ANY, -- the command list for this event
chars: REF TEXT, -- to hold first set of input chars for this event
savePSel: TEditDocument.Selection -- primary selection when event started
];
repeatList: PUBLIC LIST OF REF ANY;
charsClosed, charsUsed: PUBLIC BOOL ← FALSE;
chars: PUBLIC REF TEXT; -- buffer for typein
CloseEventNow:
PUBLIC
ENTRY
PROC = {
ENABLE UNWIND => NULL;
BetweenEvents:
PROC
RETURNS [
BOOL] = {
RETURN [repeatList = NIL AND UndoEvent.Empty[currentEvent]];
};
subevents: UndoEvent.SubEvent;
freeList: LIST OF TextNode.Ref;
IF BetweenEvents[]
THEN {
TEditSelection.Copy[source: TEditSelection.pSel, dest: editEvent.savePSel];
RETURN;
}; -- already closed
editEvent.repeatList ← repeatList; repeatList ← NIL;
now move to the next editEvent
editEvent ← editEvent.next;
TEditSelection.Copy[source: TEditSelection.pSel, dest: editEvent.savePSel];
editEvent.repeatList ← NIL;
charsClosed ← charsUsed ← FALSE;
chars ← editEvent.chars;
currentEvent ← editEvent.undo;
subevents ← currentEvent.subevents;
UndoEvent.Reset[currentEvent];
eventNumber ← eventNumber+1;
check for pending deletes in undo list for event
FOR sub: UndoEvent.SubEvent ← subevents, sub.next
UNTIL sub=
NIL
DO
Free:
PROC [root: TextNode.Ref] = {
FOR lst:
LIST
OF TextNode.Ref ← freeList, lst.rest
UNTIL lst=
NIL
DO
IF lst.first = root THEN RETURN;
ENDLOOP;
freeList ← CONS[root, freeList] };
IF sub.undoRef #
NIL
THEN
WITH sub.undoRef
SELECT
FROM
x: REF UndoEvent.Change.ChangingText => { IF x.root # NIL AND x.root.deleted THEN Free[x.root] };
x: REF UndoEvent.Change.ChangingProp => { IF x.root # NIL AND x.root.deleted THEN Free[x.root] };
x:
REF UndoEvent.Change.MovingNodes => {
IF x.destRoot # NIL AND x.destRoot.deleted THEN Free[x.destRoot];
IF x.sourceRoot # NIL AND x.sourceRoot.deleted THEN Free[x.sourceRoot] };
x: REF UndoEvent.Change.NodeNesting => { IF x.root # NIL AND x.root.deleted THEN Free[x.root] };
x: REF UndoEvent.Change.InsertingNode => { IF x.root # NIL AND x.root.deleted THEN Free[x.root] };
ENDCASE;
ENDLOOP;
FOR lst:
LIST
OF TextNode.Ref ← freeList, lst.rest
UNTIL lst=
NIL
DO
FreeTree[lst.first];
ENDLOOP;
};
RecordInt:
PUBLIC
ENTRY
PROC [i:
LONG
INTEGER] = {
ENABLE UNWIND => NULL;
IF TEditInput.interpreterNesting > 1 THEN RETURN;
IF charsUsed THEN charsClosed ← TRUE;
repeatList ← CONS[NEW[LONG INTEGER ← i],repeatList];
};
RecordChar:
PUBLIC
ENTRY
PROC [c:
CHAR] = {
ENABLE UNWIND => NULL;
IF TEditInput.interpreterNesting > 1 THEN RETURN;
IF ~charsClosed
THEN {
IF ~charsUsed
THEN {
charsUsed ← TRUE; chars.length ← 0;
repeatList ← CONS[chars,repeatList] };
chars[chars.length] ← c;
IF (chars.length ← chars.length+1) = chars.maxLength THEN charsClosed ← TRUE }
ELSE repeatList ← CONS[NEW[CHAR ← c],repeatList] };
RecordRef:
PUBLIC
ENTRY
PROC [ref:
REF
ANY] = {
ENABLE UNWIND => NULL;
IF TEditInput.interpreterNesting > 1 THEN RETURN;
IF charsUsed THEN charsClosed ← TRUE;
repeatList ← CONS[ref, repeatList] };
treeQueue: MonitoredQueue.MQ ← MonitoredQueue.Create[];
FreeTree:
PUBLIC
PROC [root: TextNode.Ref] = {
IF root=NIL OR root.deleted THEN RETURN;
root.deleted ← TRUE;
FOR event: EditEvent ← editEvent.next, event.next
UNTIL event = editEvent
DO
FOR sub: UndoEvent.SubEvent ← editEvent.undo.subevents, sub.next
UNTIL sub=
NIL
DO
IF sub.undoRef #
NIL
THEN
WITH sub.undoRef
SELECT
FROM
x: REF UndoEvent.Change.ChangingText => IF x.root = root THEN RETURN;
x: REF UndoEvent.Change.ChangingProp => IF x.root = root THEN RETURN;
x: REF UndoEvent.Change.MovingNodes => IF x.destRoot = root OR x.sourceRoot = root THEN RETURN;
x: REF UndoEvent.Change.NodeNesting => IF x.root = root THEN RETURN;
x: REF UndoEvent.Change.InsertingNode => IF x.root = root THEN RETURN;
ENDCASE;
ENDLOOP;
ENDLOOP;
MonitoredQueue.Add[root, treeQueue] };
FreeTrees:
PROC = {
DO
-- forever
tree: REF ANY ← MonitoredQueue.Remove[treeQueue];
DoFreeTree[NARROW[tree] ! ABORTED => CONTINUE];
ENDLOOP };
nodesFreed: INT ← 0;
DoFreeTree:
PROC [root: TextNode.Ref] = {
next, node: TextNode.Ref;
IF root.child = NIL THEN RETURN; -- has already been freed
CedarProcess.SetPriority[background];
DO
[] ← TEditLocks.Lock[root, "DoFreeTree", write];
must make sure no one still reading it. never unlock it.
IF TEditDocument.GetViewerForRoot[root] =
NIL
THEN
EXIT;
If there was no associated viewer when we locked, then we can leave the document locked against possible access bugs (the caller will stall)
TEditDocument.RecordViewerForRoot[
NIL, root];
If there was a viewer, then remove its association
TEditLocks.Unlock[root];
To release the lock on the locked viewer (if any)
ENDLOOP;
node ← root;
DO
-- go through the tree zapping REF's
IF node.child # NIL THEN { next ← node.child; node.child ← NIL; node ← next; LOOP };
node.deleted ← TRUE;
node.props ← NIL;
next ← node.next;
node.next ← NIL;
node.rope ← NIL;
node.runs ← NIL;
nodesFreed ← nodesFreed+1;
IF (node ← next) = NIL THEN EXIT;
ENDLOOP;
Cancel:
PUBLIC TEditInput.CommandProc = {
CloseEventNow[]; Undo[eventNumber-1]; RETURN [FALSE];
};
Undo:
PUBLIC
PROC [eventNum:
INT] = {
e, first, last: EditEvent;
viewer: ViewerClasses.Viewer;
num, undone: INT;
selectionsLocked: BOOL ← FALSE;
docList, lockedList: LIST OF TextNode.Ref;
Cleanup:
PROC = {
IF selectionsLocked THEN TEditSelection.UnlockBothSelections[];
FOR list:
LIST
OF TextNode.Ref ← lockedList, list.rest
UNTIL list=
NIL
DO
-- release the locks
TEditLocks.Unlock[list.first]; ENDLOOP;
};
CloseEventNow[];
TEditSelection.LockBothSelections["Undo"]; selectionsLocked ← TRUE;
{ ENABLE UNWIND => Cleanup[];
list: LIST OF TextNode.Ref;
num ← eventNumber; first ← e ← editEvent;
UNTIL (num ← num-1) < eventNum
DO
-- find all the documents to be changed
Add:
PROC [doc: TextNode.Ref] = {
prev: LIST OF TextNode.Ref;
IF doc=NIL THEN RETURN;
FOR list:
LIST
OF TextNode.Ref ← docList, list.rest
UNTIL list=
NIL
DO
IF list.first = doc THEN RETURN; ENDLOOP;
Not on list. Insert it in appropriate order for locking.
IF docList=
NIL
OR TEditLocks.LockOrder[doc, docList.first]
THEN {
-- doc goes at start
docList ← CONS[doc, docList];
RETURN };
prev ← docList;
FOR list:
LIST
OF TextNode.Ref ← docList, list.rest
UNTIL list=
NIL
DO
IF TEditLocks.LockOrder[doc, list.first]
THEN {
-- doc goes after prev and before this
prev.rest ← CONS[doc, list.rest];
RETURN };
prev ← list;
ENDLOOP;
prev.rest ← CONS[doc, NIL]; -- put it at the end of the list
};
sub: UndoEvent.SubEvent;
IF (e ← e.prev) = first THEN EXIT;
sub ← e.undo.subevents;
UNTIL sub=
NIL
DO
IF sub.undoRef#
NIL
THEN
WITH sub.undoRef
SELECT
FROM
x: REF UndoEvent.Change.ChangingText => Add[x.root];
x: REF UndoEvent.Change.ChangingFormat => Add[x.root];
x: REF UndoEvent.Change.ChangingProp => Add[x.root];
x: REF UndoEvent.Change.MovingNodes => { Add[x.destRoot]; Add[x.sourceRoot] };
x: REF UndoEvent.Change.NodeNesting => Add[x.root];
x: REF UndoEvent.Change.InsertingNode => Add[x.root];
ENDCASE;
sub ← sub.next;
ENDLOOP;
ENDLOOP;
list ← docList;
WHILE list #
NIL
DO
-- get write locks for them
Because of the way docList was created, can lock in order without danger of deadlock.
rest: LIST OF TextNode.Ref ← list.rest;
[] ← TEditLocks.Lock[list.first, "Undo", write];
list.rest ← lockedList; lockedList ← list; -- move item to list of locked documents
list ← rest;
ENDLOOP;
viewer ← TEditSelection.pSel.viewer;
TEditSelection.Deselect[primary]; -- get rid of the primary selection
TEditSelection.Deselect[secondary]; -- get rid of secondary selection
TEditSelection.Deselect[feedback]; -- get rid of feedback selection
num ← eventNumber; undone ← 0; first ← e ← editEvent;
UNTIL (num ← num-1) < eventNum
DO
IF (e ← e.prev) = first THEN EXIT; -- have undone all the saved events
UndoEvent.Undo[e.undo, currentEvent];
last ← e; undone ← undone+1;
ENDLOOP;
IF TEditInput.CheckSelection[last.savePSel]
THEN
TEditSelection.MakeSelection[selection: primary, new: last.savePSel]
ELSE {
-- give up the input focus
if: ViewerClasses.Viewer = InputFocus.GetInputFocus[].owner;
IF if=viewer THEN InputFocus.SetInputFocus[NIL] };
RecordInt[undone];
RecordRef[$Undone];
CloseEventNow[];
Cleanup[];
}};
CurrentEventNumber:
PUBLIC
PROC
RETURNS [
INT] = {
-- this counter is incremented at the end of each event
RETURN [eventNumber] };
CountEvents:
PROC
RETURNS[number:
INT] = {
number ← 1;
FOR e: EditEvent ← editEvent.next, e.next
UNTIL e=editEvent
DO
number ← number+1; ENDLOOP;
GetEvent:
PROC [number:
INT]
RETURNS [event: EditEvent] = {
IF eventNumber < number THEN RETURN [NIL];
IF (number ← eventNumber-number) = 0 THEN RETURN [editEvent];
FOR e: EditEvent ← editEvent.prev, e.prev
DO
IF e = editEvent THEN EXIT; -- have gone past
IF (number ← number-1) = 0 THEN RETURN [e];
ENDLOOP;
RETURN [NIL] };
SliceSize:
PUBLIC
ENTRY
PROC
RETURNS [number:
INT] = {
-- the size of the edit history buffer (number of events remembered)
ENABLE UNWIND => NULL;
RETURN[CountEvents[]];
NewSliceSize:
PUBLIC
ENTRY
PROC [number:
INT] = {
-- can change history length dynamically
ENABLE UNWIND => NULL;
delta: INT;
number ← MAX[MIN[number,200],2]; -- limit to [2..200]
delta ← number-CountEvents[];
SELECT delta
FROM
= 0 => NULL;
< 0 =>
FOR e: EditEvent ← editEvent.next, e.next
DO
-- reduce size
IF (delta ← delta+1) > 0
THEN {
-- connect to e
editEvent.next ← e; e.prev ← editEvent; RETURN };
ENDLOOP;
ENDCASE =>
-- increase size
FOR i:
INT
IN [0..delta)
DO
e: EditEvent ← CreateEvent[];
e.next ← editEvent.next; e.next.prev ← e;
e.prev ← editEvent; editEvent.next ← e;
ENDLOOP };
Known:
PUBLIC
ENTRY
PROC [number:
INT]
RETURNS [
BOOL] = {
-- returns true if event is still remembered
ENABLE UNWIND => NULL;
e: EditEvent ← GetEvent[number];
RETURN [ e # NIL AND (e.repeatList # NIL OR ~UndoEvent.Empty[e.undo]) ] };
Repeat:
PUBLIC TEditInput.CommandProc = {
list: LIST OF REF ANY;
IF TEditSelection.pSel =
NIL
OR TEditSelection.pSel.granularity = point
THEN {
MessageWindow.Append["Make selection for repeat",TRUE];
MessageWindow.Blink[] }
ELSE
IF (list ← GetRepeatSequence[]) =
NIL
THEN {
MessageWindow.Append["Nothing saved for repeat",TRUE];
MessageWindow.Blink[] }
ELSE {
DoRepeat:
PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] = {
TEditInput.InterpInput[viewer, list, FALSE] };
TEditInputOps.CallWithLocks[DoRepeat] };
RETURN [FALSE] };
GetRepeatList:
PUBLIC
ENTRY
PROC [number:
INT]
RETURNS [
LIST
OF
REF
ANY] = {
-- returns the atoms and other args stored for the event
ENABLE UNWIND => NULL;
e: EditEvent ← GetEvent[number];
RETURN [IF e = NIL THEN NIL ELSE List.Reverse[e.repeatList]] };
GetRepeatSequence:
PUBLIC
PROC
RETURNS [params:
LIST
OF
REF
ANY] = {
num: INT;
CloseEventNow[];
num ← eventNumber;
WHILE Known[num ← num-1]
DO
-- get list to repeat
IF (params ← GetRepeatList[num]) = NIL THEN LOOP; -- skip over empty entries
IF params.rest #
NIL
AND params.rest.rest =
NIL
AND params.rest.first = $Undone
THEN { params ← NIL; LOOP }; -- skip over Undo commands
RETURN;
ENDLOOP };
closeEvent: PUBLIC BOOL ← FALSE;
CreateEvent:
PROC
RETURNS [e: EditEvent] = {
e ← NEW[EditEventRec];
e.undo ← UndoEvent.Create[];
e.chars ← NEW[TEXT[64]];
e.savePSel ← NEW[TEditDocument.SelectionRec];
};
InitEvents:
PROC = {
num: INT ← MAX[MIN[UserProfile.Number["Tioga.EditHistory", 20],200],2]; -- force to [2..200]
first: EditEvent ← CreateEvent[];
prev: EditEvent ← first;
FOR i:
INT
IN [1..num)
DO
next: EditEvent ← CreateEvent[];
next.prev ← prev;
prev.next ← next;
prev ← next;
ENDLOOP;
first.prev ← prev; prev.next ← first; -- close the ring
editEvent ← first;
chars ← editEvent.chars;
currentEvent ← editEvent.undo };
RegisterCommandAtoms:
PROC = {
TEditInput.Register[$Repeat, Repeat];
TEditInput.Register[$Undo, Cancel];
};
RegisterCommandAtoms[];
InitEvents[];
TRUSTED {Process.Detach[FORK FreeTrees[]]};
END.