TEditInputEventsImpl.mesa
Copyright Ó 1985, 1991, 1992 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) June 17, 1985 2:28:28 pm PDT
Michael Plass, March 29, 1985 5:48:59 pm PST
Doug Wyatt, February 28, 1992 10:19 am PST
DIRECTORY
CedarProcess USING [SetPriority],
EditNotify USING [Change],
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 [Root, DestroyTree],
Tioga USING [Node],
UndoEvent USING [Create, Empty, Event, EventRep, Reset, SubEvent, Undo],
UserProfile USING [Number],
ViewerClasses USING [Viewer];
TEditInputEventsImpl:
CEDAR
MONITOR
IMPORTS CedarProcess, InputFocus, List, MessageWindow, MonitoredQueue, Process, TEditDocument, TEditInput, TEditInputOps, TEditLocks, TEditSelection, TextNode, UndoEvent, UserProfile
EXPORTS TEditInput, TEditHistory, Tioga
= BEGIN
Node: TYPE ~ Tioga.Node;
Event: TYPE ~ UndoEvent.Event;
EventRep: PUBLIC TYPE ~ UndoEvent.EventRep;
Change: TYPE ~ EditNotify.Change;
Global variables
currentEvent: PUBLIC Event;
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.Event, -- 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
RootAction:
TYPE ~
PROC [root: Node]
RETURNS [quit:
BOOL ¬
FALSE];
MapRootsInSubEvents:
PROC [subevents: UndoEvent.SubEvent, action: RootAction]
RETURNS [quit:
BOOL ¬
FALSE] ~ {
Do1:
PROC [node: Node] ~ {
root: Node ~ TextNode.Root[node];
quit ¬ action[root];
};
Do2:
PROC [node1, node2: Node] ~ {
root1: Node ~ TextNode.Root[node1];
root2: Node ~ TextNode.Root[node2];
quit ¬ action[root1];
IF root2#root1 AND NOT quit THEN quit ¬ action[root2];
};
FOR sub: UndoEvent.SubEvent ¬ subevents, sub.next
UNTIL quit
OR sub=
NIL
DO
WITH sub.undoRef
SELECT
FROM
x: REF Change.ChangingText => Do1[x.text];
x: REF Change.ChangingProp => Do1[x.node];
x: REF Change.MovingNodes => Do2[x.dest, x.pred];
x: REF Change.NodeNesting => Do1[x.first];
x: REF Change.InsertingNode => Do1[x.new];
ENDCASE;
ENDLOOP;
};
CloseEventNow:
PUBLIC
ENTRY
PROC = {
ENABLE UNWIND => NULL;
BetweenEvents:
PROC
RETURNS [
BOOL] = {
RETURN [repeatList = NIL AND UndoEvent.Empty[currentEvent]];
};
freeList: LIST OF Node ¬ NIL;
CheckForDeletedRoot: RootAction ~ {
IF root#
NIL
AND root.deleted
THEN {
FOR lst:
LIST
OF Node ¬ freeList, lst.rest
UNTIL lst=
NIL
DO
IF lst.first = root THEN RETURN;
ENDLOOP;
freeList ¬ CONS[root, freeList];
};
};
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;
[] ¬ MapRootsInSubEvents[currentEvent.subevents, CheckForDeletedRoot];
check for pending deletes in undo list for event
UndoEvent.Reset[currentEvent];
eventNumber ¬ eventNumber+1;
FOR lst: LIST OF Node ¬ freeList, lst.rest UNTIL lst=NIL DO FreeTree[lst.first] ENDLOOP;
};
RecordInt:
PUBLIC
ENTRY
PROC [i:
INT] = {
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: Node] = {
thisRoot: Node ~ root;
Check: RootAction ~ { RETURN[root=thisRoot] };
IF root=NIL OR root.deleted THEN RETURN;
root.deleted ¬ TRUE;
FOR event: EditEvent ¬ editEvent.next, event.next
UNTIL event = editEvent
DO
IF MapRootsInSubEvents[editEvent.undo.subevents, Check] THEN RETURN;
ENDLOOP;
MonitoredQueue.Add[root, treeQueue];
};
FreeTrees:
PROC = {
CedarProcess.SetPriority[background];
DO
-- forever
WITH MonitoredQueue.Remove[treeQueue]
SELECT
FROM
root: Node => DoFreeTree[root ! ABORTED => CONTINUE];
ENDCASE;
ENDLOOP;
};
DoFreeTree:
PROC [root: Node] = {
IF root=NIL OR root.child=NIL THEN RETURN; -- has already been freed
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;
TextNode.DestroyTree[root];
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 Node;
Cleanup:
PROC = {
IF selectionsLocked THEN TEditSelection.UnlockBothSelections[];
FOR list:
LIST
OF Node ¬ 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 Node;
num ¬ eventNumber; first ¬ e ¬ editEvent;
UNTIL (num ¬ num-1) < eventNum
DO
-- find all the documents to be changed
Add: RootAction = {
doc: Node ~ root;
prev: LIST OF Node;
IF doc=NIL THEN RETURN;
FOR list:
LIST
OF Node ¬ 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 Node ¬ 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
};
IF (e ¬ e.prev) = first THEN EXIT;
[] ¬ MapRootsInSubEvents[e.undo.subevents, Add];
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 Node ¬ 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 NOT 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: Node, 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[];
Process.Detach[FORK FreeTrees[]];
END.