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] = { 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] = { ENABLE UNWIND => NULL; RETURN[CountEvents[]]; }; NewSliceSize: PUBLIC ENTRY PROC [number: INT] = { 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] = { 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] = { 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. TEditInputEventsImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Russ Atkinson (RRA) June 17, 1985 2:28:28 pm PDT Doug Wyatt, March 3, 1985 6:18:34 pm PST Michael Plass, March 29, 1985 5:48:59 pm PST Global variables now move to the next editEvent check for pending deletes in undo list for event must make sure no one still reading it. never unlock it. If there was no associated viewer when we locked, then we can leave the document locked against possible access bugs (the caller will stall) If there was a viewer, then remove its association To release the lock on the locked viewer (if any) 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 ΚΩ˜codešœ™Kšœ Οmœ1™žœžœž˜Qš žœžœžœžœ žœž˜6Kš œžœ"žœžœžœ˜EKš œžœ"žœžœžœ˜EKš œžœ!žœžœžœžœ˜_Kš œžœ!žœžœžœ˜DKš œžœ#žœžœžœ˜FKšžœ˜—Kšžœ˜—Kšžœ˜—K˜&K˜—š‘ œžœ˜šžœ  ˜ Kšœžœžœ$˜1Kšœ žœ žœžœ˜/Kšžœ˜ K˜——Kšœ žœ˜š‘ œžœ˜)K˜Kš žœžœžœžœ ˜:Kšœ%˜%K˜šž˜šœ0˜0Kšœ8™8—šžœ(žœžœžœ˜8KšœŒ™Œ—šœ"žœ˜-K™2—šœ˜Kšœ1™1—Kšžœ˜—K˜ šžœ $˜'Kš žœžœžœ#žœžœ˜TKšœžœ˜Kšœ žœ˜Kšœ˜Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜K˜Kšžœžœžœžœ˜!Kšžœ˜—šœ˜K˜——šœžœ˜)Kšœ&žœžœ˜5Kšœ˜—K˜š‘œžœžœ žœ˜%K˜K˜Kšœ žœ˜Kšœžœžœ˜Kšœžœžœ˜*š‘œžœ˜Kšžœžœ'˜?š žœžœžœ&žœžœžœ ˜]Kšœžœ˜'—K˜K˜—K˜Kšœ>žœ˜CKšœžœžœ˜Kšœžœžœ˜K˜)šžœžœ '˜Iš‘œžœ˜!Kšœžœžœ˜Kšžœžœžœžœ˜š žœžœžœ#žœžœž˜EKšžœžœžœžœ˜)—K™9š žœ žœžœ*žœ ˜VKšœ žœ˜Kšžœ˜ —Kšœ˜š žœžœžœ#žœžœž˜Ešžœ'žœ &˜VKšœ žœ˜!Kšžœ˜ —K˜ Kšžœ˜—Kšœ žœžœ  ˜Kšœžœ˜—šœ˜K˜——š‘œžœ žœžœ˜;Kšžœžœžœžœ˜*Kšžœ#žœžœ ˜=šžœ'ž˜,Kšžœžœžœ ˜-Kšžœžœžœ˜+Kšžœ˜—Kšžœžœ˜K˜—š ‘ œžœžœžœžœ žœ˜6KšœD™DKšžœžœžœ˜Kšžœ˜šœ˜K˜——š ‘ œžœžœžœ žœ˜1Kšœ(™(Kšžœžœžœ˜Kšœžœ˜ Kšœ žœžœ ˜6Kšœ˜šžœž˜Kšœžœ˜ šœžœ'žœ ˜Bšžœžœ ˜/Kšœ(žœ˜1—Kšžœ˜—šžœ ˜šžœžœžœ žœ˜K˜K˜)K˜'Kšžœ˜ K˜————š‘œžœžœžœ žœžœžœ˜9Kšœ,™,Kšžœžœžœ˜K˜ Kš žœžœžœžœžœ˜JK˜—šœžœ˜)Kš œžœžœžœžœ˜šžœžœžœ)žœ˜NKšœ1žœ˜7K˜—šžœžœ žœžœ˜1Kšœ0žœ˜6K˜—šžœ˜š‘œžœ8˜FKšœ%žœ˜.—K˜(—Kšžœžœ˜K˜—š‘ œžœžœžœ žœžœžœžœžœžœ˜LKšœ8™8Kšžœžœžœ˜K˜ Kš žœžœžœžœžœžœ˜?K˜—š‘œžœžœžœ žœžœžœžœ˜DKšœžœ˜ K˜K˜šžœžœ ˜1Kš žœ!žœžœžœ ˜Lš žœžœžœžœžœ˜OKšžœ žœžœ ˜7—Kšžœ˜Kšžœ˜ K˜——Kšœ žœžœžœ˜ K˜š‘ œžœžœ˜,Kšœžœ˜K˜Kšœ žœžœ˜Kšœ žœ˜-K˜K˜—š‘ œžœ˜Kšœžœžœžœ6 ˜\K˜!K˜šžœžœžœ ž˜K˜ K˜K˜K˜ Kšžœ˜—Kšœ& ˜7K˜K˜K˜ K˜—š‘œžœ˜Kšœ%˜%Kšœ#˜#K˜K˜—K˜K˜ Kšžœžœ˜+K˜Kšžœ˜—…—0Dο