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