<<>> <> <> <> <> <> 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; <> 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; <<>> <> 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]; <> 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]; <> IF TEditDocument.GetViewerForRoot[root] = NIL THEN EXIT; <> TEditDocument.RecordViewerForRoot[NIL, root]; <> TEditLocks.Unlock[root]; <> 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; <> 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 <> 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.