<<>> <> <> <> <> <> <> <> <<>> DIRECTORY Process USING [Detach, MsecToTicks, SetTimeout, SetPriority, priorityForeground], Rope USING [Concat, Fetch, FromRefText, Length, ROPE], TextEdit USING [ReplaceByRope], TextNode USING [Location, Ref], TEditDocument USING [Selection], TEditInput USING [currentEvent], TEditInputOps, TEditLocks USING [Lock, Unlock], TEditRefresh USING [ScrollToEndOfSel], TEditSelection USING [Alloc, CaretVisible, Copy, InsertionPoint, LockSel, MakePointSelection, pSel, SelectionRoot, UnlockSel]; TEditBufferedInputImpl: CEDAR MONITOR IMPORTS Process, Rope, TextEdit, TEditInput, TEditInputOps, TEditLocks, TEditRefresh, TEditSelection EXPORTS TEditInputOps = BEGIN state: { idle, -- no chars in buffer, no (selection | document) locks held charsWaiting, -- chars in buffer, nothing locked yet acquiringLocks, -- acquiring locks for primary selection and its document processingChars, -- holding locks, inserting (or discarding) chars releasingLocks -- releasing selection and document locks } ¬ idle; bufferingStarted: CONDITION; -- state became charsBuffered insertionDone: CONDITION; -- state became idle bufferMaxlen: CARDINAL ¬ 32; inputBuffer: REF TEXT ¬ NEW[TEXT[bufferMaxlen]]; inputRope: Rope.ROPE ¬ NIL; untilTimesOutInALittleWhile: CONDITION; bufferClear: CONDITION; BufferedInsertChar: PUBLIC ENTRY PROC [char: CHAR] = { ENABLE UNWIND => NULL; -- release lock count: INTEGER ¬ 0; WHILE inputBuffer.length >= bufferMaxlen DO -- buffer full IF (count ¬ count+1) > 10 THEN { -- waited long enough inputRope ¬ Rope.Concat[inputRope, Rope.FromRefText[inputBuffer]]; inputBuffer.length ¬ 0; EXIT }; <> WAIT bufferClear; -- this will time out if repaint fails to empty the buffer ENDLOOP; inputBuffer[inputBuffer.length] ¬ char; inputBuffer.length ¬ inputBuffer.length + 1; IF state=idle THEN { state ¬ charsWaiting; NOTIFY bufferingStarted }; }; BufferedInsertText: PUBLIC PROC [text: Rope.ROPE] = { FOR n: INT IN [0..Rope.Length[text]) DO BufferedInsertChar[Rope.Fetch[text, n]]; ENDLOOP; }; WaitForInsertToFinish: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; -- release lock UNTIL state=idle DO WAIT insertionDone; ENDLOOP; }; BufferedInputInsertionProcess: PROC ~ { tSel: TEditDocument.Selection ~ TEditSelection.Alloc[]; -- scratch storage for MakeEdits Process.SetPriority[Process.priorityForeground]; --[CJ] DO editsOK: BOOL ¬ FALSE; -- true if the selected document is not readonly root: TextNode.Ref ¬ NIL; -- root of document containing the primary selection rootLocked: BOOL ¬ FALSE; -- true iff root has been locked WaitForCharsToArrive: ENTRY PROC ~ { ENABLE UNWIND => NULL; UNTIL state=charsWaiting DO WAIT bufferingStarted; ENDLOOP; state ¬ acquiringLocks; }; ProcessCharsInBuffer: ENTRY PROC ~ { ENABLE UNWIND => state ¬ releasingLocks; state ¬ processingChars; IF root#NIL THEN { -- pSel is in an editable document caretVisible: BOOL ~ TEditSelection.CaretVisible[]; WHILE MakeEdits[root, tSel, caretVisible] DO <> ENDLOOP; } ELSE { -- readonly or not there: throw the characters away inputBuffer.length ¬ 0; inputRope ¬ NIL; BROADCAST bufferClear; }; state ¬ releasingLocks; }; NoteInsertionDone: ENTRY PROC ~ { ENABLE UNWIND => NULL; IF inputRope=NIL AND inputBuffer.length=0 THEN { state ¬ idle; BROADCAST insertionDone; } ELSE { state ¬ charsWaiting }; -- got some more while releasing locks }; <> WaitForCharsToArrive[]; TEditSelection.LockSel[primary, "TEditBufferedInputImpl"]; editsOK ¬ TEditInputOps.CheckReadonly[TEditSelection.pSel]; -- I kid you not!! IF editsOK THEN root ¬ TEditSelection.SelectionRoot[TEditSelection.pSel]; IF root#NIL THEN { [] ¬ TEditLocks.Lock[root, "TEditBufferedInputImpl"]; rootLocked ¬ TRUE; }; ProcessCharsInBuffer[! ABORTED => CONTINUE]; TEditSelection.UnlockSel[primary]; IF rootLocked THEN TEditLocks.Unlock[root]; NoteInsertionDone[]; ENDLOOP; }; MakeEdits: INTERNAL PROC [root: TextNode.Ref, tSel: TEditDocument.Selection, caretVisible: BOOL] RETURNS [BOOL] = { pos: TextNode.Location ¬ TEditSelection.InsertionPoint[TEditSelection.pSel]; IF root=NIL OR pos.node=NIL OR (inputRope=NIL AND inputBuffer.length=0) THEN RETURN[FALSE]; TEditSelection.Copy[source: TEditSelection.pSel, dest: tSel]; [] ¬ TextEdit.ReplaceByRope[root: root, dest: pos.node, start: pos.where, len: 0, rope: Rope.FromRefText[inputBuffer], looks: tSel.looks, event: TEditInput.currentEvent]; IF inputRope#NIL THEN [] ¬ TextEdit.ReplaceByRope[root: root, dest: pos.node, start: pos.where, len: 0, rope: inputRope, looks: tSel.looks, event: TEditInput.currentEvent]; pos.where ¬ pos.where+inputBuffer.length+Rope.Length[inputRope]; TEditSelection.MakePointSelection[tSel, pos]; -- side-effects the primary selection! IF caretVisible THEN { <> TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE]; }; inputBuffer.length ¬ 0; inputRope ¬ NIL; BROADCAST bufferClear; RETURN[TRUE]; }; TRUSTED { Process.SetTimeout[@untilTimesOutInALittleWhile, Process.MsecToTicks[500] --[DN]--]; Process.SetTimeout[@bufferClear, Process.MsecToTicks[50]]; --[CJ] Process.Detach[FORK BufferedInputInsertionProcess[]]; }; END.