-- TEditInputEventsImpl.mesa <> <> <> 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; <> 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 <> 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.