-- 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 BOOLEAN ← FALSE;
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: 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 ← 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 BOOL ← FALSE;
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: INT ← MAX[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.