<> <> <> <> <> <> <<>> DIRECTORY Carets USING [StartCaret, StopCaret], EditSpan USING [CompareNodeOrder, NodeOrder], Imager USING [Color, Context, MaskRectangleI, SetColor], ImagerBackdoor USING [MakeStipple], InputFocus USING [Focus, GetInputFocus, SetInputFocus], Process USING [Detach], Rope USING [ROPE], TextEdit USING [FetchChar, Size], TextLooks USING [Looks, noLooks], TextNode USING [FirstChild, ForwardClipped, LastWithin, Level, Location, NarrowToTextNode, Offset, Parent, Ref, RefTextNode, Root, Span, StepBackward, StepForward], TEditDocument USING [BeforeAfter, LineTable, maxClip, PunctuationPosition, SelectionGrain, SelectionId, SelectionPoint, Selection, TEditDocumentData], TEditSelection USING [Alloc, Copy, Create, Free, IsDown, ForceDown, LockBothSelections, LockSel, fSel, nilSel, pSel, sSel, UnlockBothSelections, UnlockSel], TEditSelectionPrivate USING [CharPositionInCachedLine], TEditTouchup USING [LockAfterRefresh, UnlockAfterRefresh], ViewerLocks USING [CallUnderWriteLock], ViewerOps USING [AddProp, FetchProp, PaintViewer], ViewerClasses USING [ModifyProc, Viewer]; TEditSelectionImpl: CEDAR PROGRAM IMPORTS Carets, EditSpan, Imager, ImagerBackdoor, InputFocus, Process, TextEdit, TextNode, TEditSelection, TEditSelectionPrivate, TEditTouchup, ViewerLocks, ViewerOps EXPORTS TEditSelection = BEGIN OPEN TEditSelection; ROPE: TYPE = Rope.ROPE; Selection: TYPE = TEditDocument.Selection; Viewer: TYPE = ViewerClasses.Viewer; <> <<>> MakePointSelection: PUBLIC PROC [selection: Selection, pos: TextNode.Location] = { <> tSel: Selection _ TEditSelection.Alloc[]; Copy[source: selection, dest: tSel]; tSel.start.pos _ tSel.end.pos _ pos; tSel.granularity _ point; tSel.pendingDelete _ FALSE; tSel.insertion _ before; MakeSelection[selection: primary, new: tSel]; Free[tSel]; }; ChangeSelections: PROC [proc: PROC [tSel: Selection], sel: Selection] = { tSel: Selection _ TEditSelection.Alloc[]; LockBothSelections["ChangeBothSelections"]; { ENABLE UNWIND => UnlockBothSelections[]; Copy[source: sel, dest: tSel]; proc[tSel] }; UnlockBothSelections[]; Free[tSel] }; PushOrExchangeSelections: PUBLIC PROC = { DoPush: PROC [tSel: Selection] = { MakeSelection[IF sSel.viewer=NIL THEN NIL ELSE sSel, primary]; MakeSelection[IF tSel.viewer=NIL THEN NIL ELSE tSel, secondary] }; ChangeSelections[DoPush, pSel] }; MakePrimary: PUBLIC PROC = { <> DoMakePrimary: PROC [tSel: Selection] = { Deselect[secondary]; MakeSelection[tSel, primary] }; ChangeSelections[DoMakePrimary, sSel] }; MakeSecondary: PUBLIC PROC = { <> DoMakeSecondary: PROC [tSel: Selection] = { Deselect[primary]; MakeSelection[tSel, secondary] ; }; ChangeSelections[DoMakeSecondary, pSel] }; CancelPrimary: PUBLIC PROC = { MakeSelection[NIL, primary]; }; CancelSecondary: PUBLIC PROC = { MakeSelection[NIL, secondary]; }; CancelFeedback: PUBLIC PROC = { MakeSelection[NIL, feedback]; }; FakeSecondary: PUBLIC PROC [sel: Selection] = { inner: PROC = { IF sSel.viewer # NIL THEN Deselect[secondary]; Copy[source: sel, dest: sSel]; }; WithLockedSelection[secondary, inner, "FakeSecondary"]; }; Deselect: PUBLIC PROC [selection: TEditDocument.SelectionId _ primary] = { <> inner: PROC = { sel: Selection = SELECT selection FROM primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR; viewer: Viewer = sel.viewer; op: ATOM = SELECT selection FROM primary => $TakeDownPSel, secondary => $TakeDownSSel, feedback => $TakeDownFSel, ENDCASE => ERROR; down: BOOL = IsDown[selection]; Copy[source: nilSel, dest: sel]; IF viewer#NIL AND NOT down THEN { <> ViewerOps.PaintViewer[viewer, client, FALSE, op]; IF NOT IsDown[selection] THEN ForceDown[selection]; <> } }; WithLockedSelection[selection, inner, "Deselect"]; }; MakeSelection: PUBLIC PROC [new: Selection _ NIL, selection: TEditDocument.SelectionId _ primary, startValid, endValid: BOOL _ FALSE, forkPaint: BOOL _ TRUE] = { <> inner: PROC = { sel: Selection; op: ATOM; SELECT selection FROM primary => { if: Viewer = InputFocus.GetInputFocus[].owner; sel _ pSel; op _ $ShowPSel; IF new=NIL THEN {IF if#NIL THEN InputFocus.SetInputFocus[NIL]} ELSE IF if#new.viewer THEN InputFocus.SetInputFocus[new.viewer]; }; feedback => { sel _ fSel; op _ $ShowFSel; }; secondary => { sel _ sSel; op _ $ShowSSel; }; ENDCASE => ERROR; IF new=NIL OR new.viewer # sel.viewer THEN Deselect[selection]; IF new#NIL THEN { IF selection#feedback AND fSel.viewer=new.viewer THEN Deselect[feedback]; <> Copy[source: new, dest: sel]; sel.start.metricsValid _ startValid; sel.end.metricsValid _ endValid; IF sel.granularity=point THEN sel.pendingDelete _ FALSE; IF forkPaint THEN { showSel: REF ShowSelRec = SELECT selection FROM primary => showPSel, secondary => showSSel, ENDCASE => showFSel; IF showSel.process = NIL THEN TRUSTED {Process.Detach[showSel.process _ FORK ShowSel[showSel, op ! ABORTED => CONTINUE]] } } ELSE ViewerOps.PaintViewer[sel.viewer, client, FALSE, op] } }; WithLockedSelection[selection, inner, "MakeSelection"]; }; ShowSelRec: TYPE = RECORD [ process: PROCESS, selection: TEditDocument.SelectionId ]; showPSel: REF ShowSelRec _ NEW[ShowSelRec _ [NIL, primary]]; showSSel: REF ShowSelRec _ NEW[ShowSelRec _ [NIL, secondary]]; showFSel: REF ShowSelRec _ NEW[ShowSelRec _ [NIL, feedback]]; ShowSel: PROC [my: REF ShowSelRec, op: ATOM] = { selection: TEditDocument.SelectionId _ my.selection; sel: Selection _ SELECT selection FROM primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR; inner: PROC = { viewer: Viewer _ sel.viewer; inner: PROC = { WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { IF NOT TEditTouchup.LockAfterRefresh[tdd, "ShowSel"] THEN RETURN; ViewerOps.PaintViewer[viewer, client, FALSE, op ! UNWIND => TEditTouchup.UnlockAfterRefresh[tdd]]; TEditTouchup.UnlockAfterRefresh[tdd]; }; ENDCASE; }; IF viewer#NIL THEN ViewerLocks.CallUnderWriteLock[inner, viewer]; my.process _ NIL }; WithLockedSelection[selection, inner, "ShowSel"]; }; lightGrey: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0208H, xor: TRUE]; darkGrey: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0A5A5H, xor: TRUE]; veryDarkGrey: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0EBEBH, xor: TRUE]; black: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0FFFFH, xor: TRUE]; SelColor: TYPE = {black, lightGrey, darkGrey, veryDarkGrey} _ black; SelBound: TYPE = {solid, line} _ solid; selectionLineWidth: NAT _ 2; feedbackLineWidth: NAT _ 4; feedbackLineRaise: INTEGER _ 1; selectionDescentLimit: NAT _ 7; <> MarkSelection: PUBLIC PROC [dc: Imager.Context, viewer: Viewer, selection: Selection, id: TEditDocument.SelectionId] = { WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { lines: TEditDocument.LineTable = tdd.lineTable; vHeight: INTEGER = selection.viewer.ch; selBound: SelBound = IF selection.pendingDelete AND id#feedback THEN solid ELSE line; selColor: SelColor = IF id=primary THEN black ELSE IF id=feedback THEN veryDarkGrey ELSE IF selection.pendingDelete THEN lightGrey ELSE darkGrey; EffectSelect: PROC [x, y, w, h: INTEGER] = { lineHeight: NAT _ selectionLineWidth; bottom: INTEGER _ vHeight-y-h; IF id=feedback THEN { lineHeight _ feedbackLineWidth; bottom _ bottom - feedbackLineRaise }; SELECT selColor FROM black => Imager.SetColor[dc, black]; lightGrey => Imager.SetColor[dc, lightGrey]; darkGrey => Imager.SetColor[dc, darkGrey]; veryDarkGrey=> Imager.SetColor[dc, veryDarkGrey]; ENDCASE => ERROR; SELECT selBound FROM line => Imager.MaskRectangleI[dc, x, bottom, w, lineHeight]; solid => Imager.MaskRectangleI[dc, x, bottom, w, vHeight-y-bottom]; ENDCASE => ERROR; }; IF selection.end.line<0 OR selection.start.line=LAST[INTEGER] OR (selection.start.line=selection.end.line AND selection.end.clipped) THEN RETURN; -- not visible SELECT TRUE FROM selection.start.line = selection.end.line OR (selection.start.line < 0 AND selection.end.line = 0) => { <> x, y: INTEGER; IF selection.start.line < 0 OR selection.start.clipped THEN { <> EffectSelect[selection.start.x, selection.start.y, (lines[sl].width + lines[sl].xOffset - selection.start.x), selection.start.h] } ELSE { <> EffectSelAll[n]; ENDLOOP; SELECT TRUE FROM selection.end.clipped => {}; -- end.line is not actually part of the selection el=selection.end.line => { EffectSelect[lines[el].xOffset, selection.end.y, selection.end.x - lines[el].xOffset + selection.end.w, selection.end.h]; }; ENDCASE => { EffectSelAll[el];-- end.line is off screen, so select all of el }; }; }; ENDCASE; }; ExtendSelection: PUBLIC PROC [dc: Imager.Context, viewer: Viewer, old, new: Selection, id: TEditDocument.SelectionId, updateEnd: BOOL] = { <> TooHard: PROC = { MarkSelection[dc, viewer, old, id]; -- take it down FixupSelection[new, viewer]; -- get position info about the new selection MarkSelection[dc, viewer, new, id]; -- put it up Copy[source: new, dest: old]; }; IF updateEnd THEN { <> BumpLoc: PROC [loc: TextNode.Location] RETURNS [TextNode.Location] = { n: TextNode.RefTextNode = TextNode.NarrowToTextNode[loc.node]; where: TextNode.Offset _ loc.where+1; IF where >= TextEdit.Size[n] THEN RETURN [[TextNode.StepForward[n],0]]; RETURN [[n,where]]; }; tooHard: BOOL = old.end.line NOT IN [0..LAST[INTEGER]); sameNode: BOOL = (new.end.pos.node = old.end.pos.node); IF tooHard THEN {TooHard; RETURN}; SELECT TRUE FROM new.end.pos=old.end.pos => {}; (sameNode AND new.end.pos.where>old.end.pos.where) OR (NOT sameNode AND EditSpan.CompareNodeOrder[new.end.pos.node, old.end.pos.node]=after) => { <> new.start _ old.end; new.start.pos _ BumpLoc[new.start.pos]; IF sameNode AND new.start.pos.node # new.end.pos.node THEN new.start.pos _ new.end.pos; <> FixupSelection[new, viewer]; MarkSelection[dc, viewer, new, id]; old.end _ new.end; } ENDCASE => { <> save: TextNode.Location = new.end.pos; new.start _ new.end; new.start.pos _ BumpLoc[save]; IF sameNode AND new.start.pos.node # new.end.pos.node THEN new.start.pos _ old.end.pos; <> new.end _ old.end; FixupSelection[new, viewer, TRUE, FALSE]; -- fix start MarkSelection[dc, viewer, new, id]; old.end _ new.start; old.end.pos _ save; FixupSelection[old, viewer, FALSE, TRUE]; }; } ELSE { <> DecLoc: PROC [loc: TextNode.Location] RETURNS [TextNode.Location] = { n: TextNode.RefTextNode; IF loc.where > 0 THEN RETURN [[loc.node, loc.where-1]]; n _ TextNode.NarrowToTextNode[TextNode.StepBackward[loc.node]]; RETURN [[n,MAX[TextEdit.Size[n],1]-1]] }; tooHard: BOOL = old.start.line NOT IN [0..LAST[INTEGER]); sameNode: BOOL = (new.start.pos.node = old.start.pos.node); IF tooHard THEN {TooHard; RETURN}; SELECT TRUE FROM new.start.pos=old.start.pos => {}; (sameNode AND new.start.pos.where { <> new.end.pos _ DecLoc[old.start.pos]; FixupSelection[new, viewer]; MarkSelection[dc, viewer, new, id]; old.start _ new.start; } ENDCASE => { <> save: TextNode.Location = new.start.pos; new.end.pos _ DecLoc[save]; new.start _ old.start; FixupSelection[new, viewer, FALSE, TRUE]; MarkSelection[dc, viewer, new, id]; old.start.pos _ save; FixupSelection[old, viewer, TRUE, FALSE]; }; }; Copy[source: old, dest: new]; }; CannotFindIt: PUBLIC ERROR = CODE; ComputeSpanLines: PUBLIC PROC [viewer: Viewer, span: TextNode.Span] RETURNS [start, end: INTEGER, startClipped, endClipped: BOOL] = { [start,startClipped] _ ComputePosLine[viewer, span.start]; SELECT TRUE FROM span.start=span.end => { end _ start; endClipped _ startClipped }; start=LAST[INTEGER] => end _ LAST[INTEGER] -- scrolled off bottom ENDCASE => { firstLine: INTEGER = IF start<=0 THEN 0 ELSE IF startClipped THEN start-1 ELSE start; [end,endClipped] _ ComputePosLine[viewer, span.end, firstLine]; }; }; ComputePosLine: PUBLIC PROC [viewer: Viewer, pos: TextNode.Location, firstLine: INTEGER _ 0] RETURNS [line: INTEGER, clipped: BOOL] = { sp: TEditDocument.SelectionPoint _ ComputePosPoint[viewer, pos, firstLine, TRUE]; RETURN [sp.line, sp.clipped]; }; ComputePosPoint: PUBLIC PROC [viewer: Viewer, pos: TextNode.Location, firstLine: INTEGER _ 0, lineOnly: BOOL _ FALSE] RETURNS [sp: TEditDocument.SelectionPoint] = { <> <> WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { foundPos: BOOL _ FALSE; IsPosPastLine: PROC [pos: TextNode.Location, line: INTEGER] RETURNS [BOOL] = { next: TextNode.Offset; next _ lines[line].pos.where+lines[line].nChars; IF pos.where >= next AND lines[line].end # eon THEN RETURN [TRUE]; -- it is past this line RETURN [FALSE] }; FindPos: PROC [pos: TextNode.Location] RETURNS [found: BOOL, line: INTEGER] = { found _ FALSE; FOR n: INTEGER IN [firstLine..lines.lastLine] DO SELECT TRUE FROM lines[n].pos.node=pos.node => { IF pos.where < lines[n].pos.where THEN EXIT; -- have gone past it found _ TRUE; line _ n }; found => EXIT; ENDCASE; ENDLOOP }; lines: TEditDocument.LineTable _ tdd.lineTable; sp.pos _ pos; IF lines[0].pos.node = pos.node THEN { IF lines[0].pos.where > pos.where THEN { <> sp.y _ sp.line _ -LAST[INTEGER]; RETURN; }; }; IF lines[lines.lastLine].pos.node=pos.node AND IsPosPastLine[pos,lines.lastLine] THEN { sp.y _ sp.line _ LAST[INTEGER]; -- after end of text RETURN; }; firstLine _ MIN[lines.lastLine,MAX[firstLine,0]]; [foundPos, sp.line] _ FindPos[pos]; sp.clipped _ FALSE; IF NOT foundPos THEN SELECT EditSpan.CompareNodeOrder[pos.node, lines[0].pos.node] FROM same, before => { sp.line _ sp.y _ -LAST[INTEGER]; RETURN }; -- before beginning disjoint => ERROR CannotFindIt; ENDCASE => -- pos.node comes after first line node IF tdd.clipLevel = TEditDocument.maxClip AND tdd.commentFilter=includeComments THEN { <> sp.line _ sp.y _ LAST[INTEGER]; RETURN } -- after end ELSE { <> SELECT EditSpan.CompareNodeOrder[pos.node, lines[lines.lastLine].pos.node] FROM same, after => { <> sp.line _ sp.y _ LAST[INTEGER]; RETURN }; disjoint => ERROR CannotFindIt; -- may have been deleted or moved ENDCASE => { -- it got clipped n: TextNode.Ref _ pos.node; delta: INTEGER _ TextNode.Level[n]-tdd.clipLevel; firstLine _ 0; -- need to let FindPos look at all the previous lines FOR i:INTEGER IN [0..delta) DO n _ TextNode.Parent[n]; ENDLOOP; n _ TextNode.ForwardClipped[n,tdd.clipLevel].nx; [foundPos, sp.line] _ FindPos[[n,0]]; IF NOT foundPos THEN ERROR CannotFindIt; <> sp.clipped _ TRUE; }; }; IF lineOnly OR sp.clipped THEN RETURN; -- otherwise, compute metrics sp.y _ lines[sp.line].yOffset-lines[sp.line].ascent; sp.h _ lines[sp.line].ascent+MIN[lines[sp.line].descent, selectionDescentLimit]; [sp.x, sp.w] _ TEditSelectionPrivate.CharPositionInCachedLine[viewer, sp.line, pos]; IF sp.x = LAST[INTEGER] THEN { <> IF sp.line=lines.lastLine THEN {sp.line _ sp.y _ LAST[INTEGER]} ELSE {sp.x _ lines[sp.line].width; sp.w _ 0}; }; sp.metricsValid _ TRUE; }; ENDCASE; }; FixupSelection: PUBLIC PROC [selection: Selection, viewer: Viewer, start, end: BOOL _ TRUE] = { <> <> IF start THEN selection.start _ ComputePosPoint[viewer, selection.start.pos]; IF end THEN SELECT TRUE FROM selection.start.pos=selection.end.pos => <> selection.end _ selection.start; selection.start.line=LAST[INTEGER] => { <> selection.end.line _ LAST[INTEGER]; selection.end.metricsValid _ TRUE; }; ENDCASE => { firstLine: INTEGER = ( IF selection.start.line<=0 THEN 0 ELSE IF selection.start.clipped THEN selection.start.line-1 ELSE selection.start.line ); selection.end _ ComputePosPoint[viewer, selection.end.pos, firstLine]; }; }; FixupCaret: PUBLIC PROC [selection: Selection] = { <> lines: TEditDocument.LineTable = selection.data.lineTable; node: TextNode.RefTextNode _ TextNode.NarrowToTextNode[selection.end.pos.node]; size: TextNode.Offset _ TextEdit.Size[node]; PutCaretOnNextLine: PROC = { SELECT selection.end.line FROM IN [0..lines.lastLine) => { nextLine: INTEGER _ selection.end.line+1; selection.caretX _ lines[nextLine].xOffset; selection.caretY _ selection.viewer.ch - lines[nextLine].yOffset - lines[nextLine].descent; }; = lines.lastLine => { selection.caretX _ lines[selection.end.line].xOffset; selection.caretY _ selection.viewer.ch - selection.end.y - selection.end.h - selection.end.h; }; ENDCASE => ERROR; }; IF selection.insertion=before THEN { IF selection.start.clipped THEN selection.caretY _ LAST[INTEGER] -- caret not visible <> <0 AND TextEdit.FetchChar[node, size-1]=15C>> <> ELSE { selection.caretX _ selection.start.x; selection.caretY _ IF selection.start.line NOT IN [0..lines.lastLine] THEN LAST[INTEGER] ELSE selection.viewer.ch - selection.start.y - selection.start.h; }; } ELSE SELECT TRUE FROM selection.end.clipped => <> selection.caretY _ LAST[INTEGER]; selection.end.pos.where IN [0..size) AND selection.end.line IN [0..lines.lastLine] AND TextEdit.FetchChar[node,selection.end.pos.where]=[0,15C] => PutCaretOnNextLine[]; ENDCASE => { selection.caretX _ selection.end.x+selection.end.w; selection.caretY _ ( IF selection.end.line NOT IN [0..lines.lastLine] THEN LAST[INTEGER] ELSE selection.viewer.ch - selection.end.y - selection.end.h ); }; }; KillSelection: PUBLIC PROC = { <> IF InputFocus.GetInputFocus[]#NIL THEN InputFocus.SetInputFocus[]; }; ModifyPSel: PROC [proc: PROC [root: TextNode.Ref, tSel: Selection]] = { inner: PROC = { root: TextNode.Ref _ SelectionRoot[pSel]; IF root#NIL THEN { tSel: Selection _ TEditSelection.Alloc[]; TEditSelection.Copy[source: pSel, dest: tSel]; proc[root, tSel ! UNWIND => Free[tSel]]; MakeSelection[new: tSel]; Free[tSel]; }; }; WithLockedSelection[primary, inner, "CallWithSelLock"]; }; SelectEverything: PUBLIC PROC = { <> DoSelEverything: PROC [root: TextNode.Ref, tSel: Selection] = { root _ TextNode.Root[tSel.start.pos.node]; tSel.start.pos _ [TextNode.FirstChild[root], 0]; tSel.end.pos.node _ TextNode.LastWithin[root]; tSel.end.pos.where _ TextEdit.Size[TextNode.NarrowToTextNode[tSel.end.pos.node]]-1; tSel.granularity _ branch }; ModifyPSel[DoSelEverything] }; PendingDeleteSelection: PUBLIC PROC = { DoPDel: PROC [root: TextNode.Ref, tSel: Selection] = { tSel.pendingDelete _ TRUE }; ModifyPSel[DoPDel] }; NotPendingDeleteSelection: PUBLIC PROC = { DoNotPDel: PROC [root: TextNode.Ref, tSel: Selection] = { tSel.pendingDelete _ FALSE }; ModifyPSel[DoNotPDel] }; CaretBeforeSelection: PUBLIC PROC = { DoCaretBeforeSelection: PROC [root: TextNode.Ref, tSel: Selection] = { tSel.insertion _ before }; ModifyPSel[DoCaretBeforeSelection] }; CaretAfterSelection: PUBLIC PROC = { DoCaretAfterSelection: PROC [root: TextNode.Ref, tSel: Selection] = { IF tSel.granularity # point THEN tSel.insertion _ after }; ModifyPSel[DoCaretAfterSelection] }; <> WithLockedSelection: PROC [which: TEditDocument.SelectionId, inner: PROC, why: ROPE] = { LockSel[which, why]; inner[ ! UNWIND => UnlockSel[which]]; UnlockSel[which]; }; SelectionRoot: PUBLIC PROC [s: Selection _ pSel] RETURNS [root: TextNode.Ref] = { IF s.viewer=NIL THEN RETURN [TextNode.Root[s.start.pos.node]]; WITH s.viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => RETURN [tdd.text]; ENDCASE => RETURN [NIL]; }; InputModify: PUBLIC ViewerClasses.ModifyProc = { WITH self.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { SELECT change FROM set, pop => Carets.StartCaret[self, pSel.caretX, pSel.caretY, primary]; kill => { inner: PROC = {Copy[source: pSel, dest: prop]; Deselect[primary]}; prop: Selection _ NARROW[ViewerOps.FetchProp[self, $SelectionHistory]]; Carets.StopCaret[primary]; IF prop=NIL THEN ViewerOps.AddProp[self, $SelectionHistory, prop _ Create[]]; WithLockedSelection[primary, inner, "InputModify"]; }; push => Carets.StopCaret[primary]; ENDCASE => ERROR; }; ENDCASE; }; END.