-- TEditInputEventsImpl.mesa
Last Edited by: Maxwell, January 6, 1983 11:54 am
Last Edited by: Plass, April 21, 1983 10:57 am
Last Edited by: Paxton, May 23, 1983 11:21 am
DIRECTORY
InputFocus USING [GetInputFocus, SetInputFocus],
List USING [Reverse],
MessageWindow USING [Append, Blink],
MonitoredQueue USING [Add, Create, MQ, Remove],
Process USING [Detach, priorityBackground, SetPriority, Yield],
TEditDocument USING [TEditDocumentData, Selection, SelectionId, SelectionRec],
TEditHistory,
TEditImpl,
TEditInput,
TEditInputOps,
TEditLocks USING [Lock, LockOrder, Unlock],
TEditSelection USING [Copy, Deselect, LockBothSelections, MakeSelection, pSel, UnlockBothSelections],
TextEdit USING [Ref],
TextNode USING [Body, Location, Offset, pZone, Ref],
UndoEvent USING [Change, Create, Empty, Ref, Reset, SubEvent, Undo],
UserProfile USING [Number],
ViewerClasses USING [Viewer, ViewerRec];
TEditInputEventsImpl: CEDAR MONITOR
IMPORTS InputFocus, List, MessageWindow, MonitoredQueue, Process, TEditInput, TEditInputOps, TEditLocks, TEditSelection, TextNode, UndoEvent, UserProfile
EXPORTS TEditInput, TEditHistory =
BEGIN OPEN TEditInput, TEditSelection;
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 BOOLEANFALSE;
chars: PUBLIC REF TEXT; -- buffer for typein
CloseEventNow: PUBLIC ENTRY PROC = {
ENABLE UNWIND => NULL;
BetweenEvents: PROC RETURNS [BOOLEAN] = INLINE {
RETURN [repeatList = NIL AND UndoEvent.Empty[currentEvent]] };
subevents: UndoEvent.SubEvent;
freeList: LIST OF TextNode.Ref;
IF BetweenEvents[] THEN {
TEditSelection.Copy[source: pSel, dest: editEvent.savePSel];
RETURN }; -- already closed
editEvent.repeatList ← repeatList; repeatList ← NIL;
-- now move to the next editEvent
editEvent ← editEvent.next;
TEditSelection.Copy[source: 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 interpreterNesting > 1 THEN RETURN;
IF charsUsed THEN charsClosed ← TRUE;
repeatList ← TextNode.pZone.CONS[TextNode.pZone.NEW[LONG INTEGER ← i],repeatList] };
RecordChar: PUBLIC ENTRY PROC [c: CHARACTER] = {
ENABLE UNWIND => NULL;
IF interpreterNesting > 1 THEN RETURN;
IF ~charsClosed THEN {
IF ~charsUsed THEN {
charsUsed ← TRUE; chars.length ← 0;
repeatList ← TextNode.pZone.CONS[chars,repeatList] };
chars[chars.length] ← c;
IF (chars.length ← chars.length+1) = chars.maxLength THEN charsClosed ← TRUE }
ELSE repeatList ← TextNode.pZone.CONS[TextNode.pZone.NEW[CHARACTER ← c],repeatList] };
RecordRef: PUBLIC ENTRY PROC [ref: REF ANY] = {
ENABLE UNWIND => NULL;
IF interpreterNesting > 1 THEN RETURN;
IF charsUsed THEN charsClosed ← TRUE;
repeatList ← TextNode.pZone.CONS[ref, repeatList] };
treeQueue: MonitoredQueue.MQ ← MonitoredQueue.Create[TextNode.pZone];
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
TRUSTED {Process.SetPriority[Process.priorityBackground]}; -- no rush to finish this
Process.Yield[];
[] ← TEditLocks.Lock[root, "DoFreeTree", write]; -- must make sure no one still reading it. never unlock it.
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;
WITH node SELECT FROM
x: REF TextNode.Body.text => { x.rope ← NIL; x.runs ← NIL };
x: REF TextNode.Body.other => x.info ← NIL;
ENDCASE;
nodesFreed ← nodesFreed+1;
IF (node ← next) = NIL THEN EXIT;
ENDLOOP };
Cancel: PUBLIC CommandProc = { CloseEventNow[]; Undo[eventNumber-1]; RETURN [FALSE] };
Undo: PUBLIC PROC [eventNum: INT] = {
e, first, last: EditEvent;
viewer: ViewerClasses.Viewer;
num, undone: INT;
selectionsLocked: BOOLFALSE;
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 ← TextNode.pZone.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 ← TextNode.pZone.CONS[doc, list.rest];
RETURN };
prev ← list;
ENDLOOP;
prev.rest ← TextNode.pZone.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.ChangingType => 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 ← pSel.viewer;
Deselect[primary]; -- get rid of the primary selection
Deselect[secondary]; -- get rid of secondary selection
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 CheckSelection[last.savePSel] THEN 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] };
SliceSize: PUBLIC ENTRY PROC RETURNS [number: INT] = {
-- the size of the edit history buffer (number of events remembered)
ENABLE UNWIND => NULL;
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] };
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-SliceSize[];
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 [BOOLEAN] = {
-- 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 CommandProc = {
list: LIST OF REF ANY;
IF pSel = NIL OR 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: TextEdit.Ref, tSel: TEditDocument.Selection] = {
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 BOOLFALSE;
CreateEvent: PROC RETURNS [e: EditEvent] = {
e ← TextNode.pZone.NEW[EditEventRec];
e.undo ← UndoEvent.Create[];
e.chars ← TextNode.pZone.NEW[TEXT[64]];
e.savePSel ← TextNode.pZone.NEW[TEditDocument.SelectionRec];
};
InitEvents: PROC = {
num: INTMAX[MIN[UserProfile.Number["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 = {
Register[$Repeat, Repeat];
Register[$Undo,Cancel];
};
RegisterCommandAtoms;
InitEvents;
TRUSTED {Process.Detach[FORK FreeTrees[]]};
END.