-- 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; 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; 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] = { RETURN [eventNumber] }; SliceSize: PUBLIC ENTRY PROC RETURNS [number: INT] = { 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] = { 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] = { 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] = { 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. ˆ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 -- now move to the next editEvent -- check for pending deletes in undo list for event Not on list. Insert it in appropriate order for locking. Because of the way docList was created, can lock in order without danger of deadlock. -- this counter is incremented at the end of each event -- the size of the edit history buffer (number of events remembered) -- can change history length dynamically -- returns true if event is still remembered -- returns the atoms and other args stored for the event ÊÒ˜JšÏc˜J™1J™.Jšœœ™-JšÏk ˜ ˜Jšœ žœ ˜0Jšœžœ ˜Jšœžœ˜$Jšœžœ˜/Jšœžœ2˜?Jšœžœ;˜NJ˜ J˜ J˜ Jšœ˜Jšœ žœ˜+JšœžœQ˜eJšœ žœ˜Jšœ žœ&˜4Jšœ žœ5˜DJšœ žœ ˜Jšœžœ˜(—J˜Jšœž ˜#J˜Jšžœ’˜™J˜Jšžœ˜"J˜Jšžœžœ˜&J˜Jšœžœ˜#Jšœ˜/Jšœ žœ%˜;Jšœ žœžœ˜#šœžœžœ˜Jšœ+˜BJšœ˜4Jš œ žœžœžœžœ"˜?Jšœžœžœ2˜CJšœ"'˜IJ˜—J˜Jš œ ž œžœžœžœ˜#Jšœžœžœ˜/Jšœž œžœ˜,J˜šÏn œžœ˜$Jšžœžœžœ˜š Ÿ œžœžœžœžœ˜0Jšžœžœžœ"˜>—J˜Jšœ žœžœ˜šžœžœ˜J˜žœžœž˜Qš žœžœžœžœ žœž˜6Jš œžœ"žœžœžœ˜EJš œžœ"žœžœžœ˜EJš œžœ!žœžœžœžœ˜_Jš œžœ!žœžœžœ˜DJš œžœ#žœžœžœ˜FJšžœ˜—Jšžœ˜—Jšžœ˜—J˜&J˜—šŸ œžœ˜šžœ ˜ Jšœžœžœ$˜1Jšœ žœ žœžœ˜/Jšžœ˜ J˜——Jšœ žœ˜šŸ œžœ˜)J˜Jš žœžœžœžœ˜:Jšžœ4˜TJ˜Jšœ1;˜lJ˜ šžœ$˜'Jš žœžœžœ#žœžœ˜TJšœžœ˜Jšœ žœ˜Jšœžœ˜"šžœžœž˜Jšœžœ"žœ žœ˜žœ˜CJšœžœžœ˜Jšœžœžœ˜J˜)šžœžœ'˜IšŸœžœ˜!Jšœžœžœ˜Jšžœžœžœžœ˜š žœžœžœ#žœžœž˜EJšžœžœžœžœ˜)—J™9š žœ žœžœ*žœ˜VJšœžœ˜,Jšžœ˜ —Jšœ˜š žœžœžœ#žœžœž˜Ešžœ'žœ&˜VJšœžœ˜0Jšžœ˜ —J˜ Jšžœ˜—Jšœžœžœ ˜KJšœ˜—J˜Jšžœžœžœ˜"Jšœ˜šžœžœž˜š žœ žœžœžœ žœž˜4Jšœžœ.˜4Jšœžœ.˜4Jšœžœ.˜4JšœžœH˜NJšœžœ-˜3Jšœžœ/˜5Jšžœ˜—Jšœ˜Jšžœ˜—Jšžœ˜—Jšœ˜šžœžœžœ˜1J™UJšœžœžœ˜'Jšœ0˜0Jšœ+(˜SJšœ ˜ Jšžœ˜—J˜Jšœ#˜6Jšœ!˜6Jšœ ˜4J˜5šžœž˜!Jšžœžœžœ#˜FJ˜%J˜Jšžœ˜—Jšžœžœ6˜[šžœ˜!J˜Jšœžœ˜J˜——šŸœžœ žœžœ˜;Jšžœžœžœžœ˜*Jšžœ#žœžœ ˜=šžœ'ž˜,Jšžœžœžœ˜-Jšžœžœžœ˜+Jšžœ˜—Jšžœžœ˜J˜—šŸ œžœž œ žœ˜1Jš(™(Jšžœžœžœ˜Jšœžœ˜ Jšœ žœžœ˜6J˜šžœž˜Jšœžœ˜ šœžœ'žœ˜Bšžœžœ˜/Jšœ(žœ˜1—Jšžœ˜—šžœ˜šžœžœžœ žœ˜J˜J˜)J˜'Jšžœ˜ J˜————š Ÿœžœž œ žœžœžœ˜