<> <> <> <> <> <> <> <> <> <<>> DIRECTORY Atom USING [GetPName, MakeAtom], EditSpan USING [CompareNodeOrder], NodeAddrs USING [GetTextAddr, MapTextAddrs, PutTextAddr, RemTextAddr, TextAddrNotFound], NodeStyleOps USING [StyleNameForNode], Rope USING [IsEmpty, ROPE, Size], TEditDocument USING [Selection, SelectionGrain, SelectionId, TEditDocumentData], TEditInput USING [Cancel, CurrentEvent, MakePointSelection, Repeat, RestoreSelectionA, RestoreSelectionB, SaveSelectionA, SaveSelectionB], TEditInputOps USING [BackSpace, BackWord, Break, Capitalise, ChangeCaretLooks, ChangeLooks, Copy, CopyLooks, CopyFormat, Delete, DeleteNextChar, DeleteNextWord, ExpandAbbreviation, GetFormat, GoToNextChar, GoToNextNode, GoToNextWord, GoToPreviousChar, GoToPreviousNode, GoToPreviousWord, InsertBrackets, InsertChar, InsertLineBreak, InsertRope, InsertTime, Join, MakeControlCharacter, MakeOctalCharacter, Nest, Paste, RegisterAbbrevFailedProc, SaveForPaste, SaveSpanForPaste, SetCommentProp, SetStyleName, SetFormat, SetFormatName, Transpose, UnMakeControlCharacter, UnMakeOctalCharacter, UnNest], TEditLocks USING [Lock, Unlock], TEditMesaOps USING [SetMesaLooksOp], TEditOps USING [CaretLoc, GetSelContents, RegisterFileNameProc], TEditSelection USING [Alloc, CaretAfterSelection, CaretBeforeSelection, Deselect, DoFind, FindWhere, Free, fSel, GrowSelection, GrowSelectionToBlanks, GrowSelectionToSomething, LockSel, MakeSelection, pSel, SelectionRoot, SetSelLooks, sSel, UnlockSel], TextEdit USING [ChangeStyle, ChangeFormat, FetchLooks, PutProp, Size], TextLooks USING [allLooks, Looks, LooksToRope, noLooks, RopeToLooks], TextNode USING [BadArgs, Body, Location, LocOffset, LocRelative, NarrowToTextNode, NodeItself, Ref, RefTextNode, Root, StepForward], TiogaExtraOps USING [], TiogaOps USING [FirstChild, GetRope, LastChild, LastWithin, SearchDir, SelectionErrorCode, ViewerDoc, WhichLooks], TiogaOpsDefs USING [Location, NodeBody, Offset, Ref, SelectionGrain, Viewer, WhichNodes, WhichSelection]; TiogaOpsImpl: CEDAR MONITOR IMPORTS Atom, EditSpan, NodeAddrs, NodeStyleOps, Rope, TEditInput, TEditInputOps, TEditLocks, TEditMesaOps, TEditOps, TEditSelection, TextEdit, TextLooks, TextNode, TiogaOps EXPORTS TiogaOpsDefs, TiogaOps, TiogaExtraOps = BEGIN OPEN TiogaOps, TiogaOpsDefs; ROPE: TYPE = Rope.ROPE; Ref: TYPE = REF NodeBody; -- points to a Tioga node NodeBody: PUBLIC TYPE = TextNode.Body; <> CallWithLocks: PUBLIC PROC [proc: PROC [root: Ref], root: Ref _ NIL] = { lockedSel, lockedDoc: BOOL _ FALSE; Cleanup: PROC = { IF lockedSel THEN { UnlockSel; lockedSel _ FALSE }; IF lockedDoc THEN { Unlock[root]; lockedDoc _ FALSE }; }; { ENABLE UNWIND => Cleanup; LockSel; lockedSel _ TRUE; IF root=NIL AND (root _ SelectionRoot[])=NIL THEN { Cleanup; ERROR NoSelection }; Lock[root]; lockedDoc _ TRUE; proc[root] }; Cleanup }; NoSelection: PUBLIC ERROR = CODE; -- raised by CallWithLocks when there is no selection Lock: PUBLIC PROC [root: Ref] = { [] _ TEditLocks.Lock[root, "TiogaOpsClient"] }; Unlock: PUBLIC PROC [root: Ref] = { TEditLocks.Unlock[root] }; GetSelectionId: PROC [which: WhichSelection] RETURNS [TEditDocument.SelectionId] = { RETURN [SELECT which FROM primary => primary, secondary => secondary, feedback => feedback, ENDCASE => ERROR ] }; LockSel: PUBLIC PROC [which: WhichSelection _ primary] = { TEditSelection.LockSel[GetSelectionId[which], "TiogaOpsClient"] }; UnlockSel: PUBLIC PROC [which: WhichSelection _ primary] = { TEditSelection.UnlockSel[GetSelectionId[which]] }; <> DocGran: PROC [granularity: SelectionGrain] RETURNS [TEditDocument.SelectionGrain] = { RETURN [SELECT granularity FROM point => point, char => char, word => word, node => node, branch => branch, ENDCASE => ERROR] }; MyGran: PROC [granularity: TEditDocument.SelectionGrain] RETURNS [SelectionGrain] = { RETURN [SELECT granularity FROM point => point, char => char, word => word, node => node, branch => branch, ENDCASE => ERROR] }; DocLoc: PROC [loc: Location] RETURNS [TextNode.Location] = { RETURN [[loc.node, loc.where]] }; MyLoc: PROC [loc: TextNode.Location] RETURNS [Location] = { RETURN [[loc.node, loc.where]] }; GetSelection: PUBLIC PROC [which: WhichSelection _ primary] RETURNS [ viewer: Viewer, start, end: Location, level: SelectionGrain, caretBefore: BOOL, pendingDelete: BOOL] = { sel: TEditDocument.Selection = SELECT which FROM primary => TEditSelection.pSel, secondary => TEditSelection.sSel, feedback => TEditSelection.fSel, ENDCASE => ERROR; RETURN [sel.viewer, MyLoc[sel.start.pos], MyLoc[sel.end.pos], MyGran[sel.granularity], sel.insertion=before, sel.pendingDelete] }; SelectionRoot: PUBLIC PROC [which: WhichSelection _ primary] RETURNS [root: Ref] = { RETURN [TextNode.NarrowToTextNode[ TEditSelection.SelectionRoot[ SELECT which FROM primary => TEditSelection.pSel, secondary => TEditSelection.sSel, feedback => TEditSelection.fSel, ENDCASE => ERROR]]] }; SelectionError: PUBLIC ERROR [ec: SelectionErrorCode] = CODE; CheckSelection: PROC [sel: TEditDocument.Selection] = { root, first, last: TextNode.Ref; t1, t2: TextNode.RefTextNode; tdd: TEditDocument.TEditDocumentData; IF sel.viewer=NIL OR sel.viewer.destroyed OR (tdd _ NARROW[sel.viewer.data])=NIL OR (root _ tdd.text)=NIL THEN ERROR SelectionError[IllegalViewer]; IF (first _ sel.start.pos.node)=NIL OR (last _ sel.end.pos.node)=NIL THEN ERROR SelectionError[IllegalNode]; IF TextNode.Root[first] # root THEN ERROR SelectionError[WrongDoc]; IF first # last THEN -- make sure nodes in same tree and right order IF EditSpan.CompareNodeOrder[first,last] # before THEN ERROR SelectionError[WrongOrder]; IF sel.start.pos.where # TextNode.NodeItself THEN -- make sure start index is ok IF (t1 _ TextNode.NarrowToTextNode[first])=NIL OR sel.start.pos.where NOT IN [0..TextEdit.Size[t1]] THEN ERROR SelectionError[BadStartOffset]; IF sel.end.pos.where # TextNode.NodeItself THEN -- make sure end index is ok IF (t2 _ TextNode.NarrowToTextNode[last])=NIL OR sel.end.pos.where NOT IN [0..TextEdit.Size[t2]] THEN ERROR SelectionError[BadEndOffset]; IF t1 # NIL AND t1 = t2 THEN -- make sure start is not after end IF sel.start.pos.where > sel.end.pos.where THEN ERROR SelectionError[BadEndOffset]; }; SetSelection: PUBLIC PROC [viewer: Viewer, start, end: Location, level: SelectionGrain _ char, caretBefore: BOOL _ TRUE, pendingDelete: BOOL _ FALSE, which: WhichSelection _ primary] = { ENABLE UNWIND => NULL; tempSel: TEditDocument.Selection _ TEditSelection.Alloc[]; tempSel^ _ SELECT which FROM primary => TEditSelection.pSel^, secondary => TEditSelection.sSel^, feedback => TEditSelection.fSel^, ENDCASE => ERROR; tempSel.viewer _ viewer; tempSel.data _ NARROW[viewer.data]; tempSel.start.pos _ DocLoc[start]; tempSel.end.pos _ DocLoc[end]; tempSel.granularity _ DocGran[level]; tempSel.insertion _ IF caretBefore OR tempSel.granularity=point THEN before ELSE after; tempSel.pendingDelete _ pendingDelete; CheckSelection[tempSel]; TEditSelection.MakeSelection[tempSel, SELECT which FROM primary => primary, secondary => secondary, feedback => feedback, ENDCASE => ERROR]; TEditSelection.Free[tempSel] }; SelectNodes: PUBLIC PROC [viewer: Viewer, start, end: Ref, level: SelectionGrain _ node, caretBefore: BOOL _ TRUE, pendingDelete: BOOL _ FALSE, which: WhichSelection _ primary] = { SetSelection[viewer, [start,0], [end, MAX[Rope.Size[GetRope[end]],1]-1], level, caretBefore, pendingDelete, which] }; SelectBranches: PUBLIC PROC [viewer: Viewer, start, end: Ref, level: SelectionGrain _ node, caretBefore: BOOL _ TRUE, pendingDelete: BOOL _ FALSE, which: WhichSelection _ primary] = { SelectNodes[viewer, start, LastWithin[end], level, caretBefore, pendingDelete, which] }; SelectDocument: PUBLIC PROC [viewer: Viewer, level: SelectionGrain _ node, caretBefore: BOOL _ TRUE, pendingDelete: BOOL _ FALSE, which: WhichSelection _ primary] = { tdd: TEditDocument.TEditDocumentData _ NARROW[viewer.data]; root: Ref _ ViewerDoc[viewer]; SelectBranches[viewer, FirstChild[root], LastChild[root], level, caretBefore, pendingDelete, which] }; CancelSelection: PUBLIC PROC [which: WhichSelection _ primary] = { TEditSelection.Deselect[GetSelectionId[which]] }; SaveSelA: PUBLIC PROC = { [] _ TEditInput.SaveSelectionA[] }; RestoreSelA: PUBLIC PROC = { [] _ TEditInput.RestoreSelectionA[] }; SaveSelB: PUBLIC PROC = { [] _ TEditInput.SaveSelectionB[] }; RestoreSelB: PUBLIC PROC = { [] _ TEditInput.RestoreSelectionB[] }; GrowSelection: PUBLIC PROC = { TEditSelection.GrowSelection[] }; GrowSelectionToBlanks: PUBLIC PROC = { TEditSelection.GrowSelectionToBlanks[] }; GrowSelectionToSomething: PUBLIC PROC [left, right: PROC [CHAR] RETURNS [BOOLEAN]] = { TEditSelection.GrowSelectionToSomething[left, right] }; <> SearchWhere: PROC [whichDir: SearchDir] RETURNS [TEditSelection.FindWhere] = { RETURN [SELECT whichDir FROM forwards => forwards, backwards => backwards, anywhere => anywhere, ENDCASE => ERROR] }; FindText: PUBLIC PROC [viewer: Viewer, rope: ROPE _ NIL, whichDir: SearchDir _ forwards, which: WhichSelection _ primary, case: BOOL _ TRUE -- case => case of characters is significant -- ] RETURNS [found: BOOL] = { IF rope=NIL THEN rope _ TEditOps.GetSelContents[]; RETURN [TEditSelection.DoFind[ viewer: viewer, rope: rope, id: GetSelectionId[which], case: case, findWhere: SearchWhere[whichDir]]] }; FindWord: PUBLIC PROC [viewer: Viewer, rope: ROPE _ NIL, whichDir: SearchDir _ forwards, which: WhichSelection _ primary, case: BOOL _ TRUE -- case => case of characters is significant -- ] RETURNS [found: BOOL] = { IF rope=NIL THEN rope _ TEditOps.GetSelContents[]; RETURN [TEditSelection.DoFind[viewer: viewer, rope: rope, case: case, id: GetSelectionId[which], findWhere: SearchWhere[whichDir], word: TRUE]] }; FindDef: PUBLIC PROC [viewer: Viewer, rope: ROPE _ NIL, whichDir: SearchDir _ forwards, which: WhichSelection _ primary, case: BOOL _ TRUE -- case => case of characters is significant -- ] RETURNS [found: BOOL] = { IF rope=NIL THEN rope _ TEditOps.GetSelContents[]; RETURN [TEditSelection.DoFind[viewer: viewer, rope: rope, case: case, id: GetSelectionId[which], findWhere: SearchWhere[whichDir], word: TRUE, def: TRUE]] }; <> LocRelative: PUBLIC PROC [location: Location, count: Offset, break: NAT _ 1, skipCommentNodes: BOOL _ FALSE] RETURNS [Location] = { RETURN [MyLoc[TextNode.LocRelative[DocLoc[location], count, break, skipCommentNodes]]]; }; LocOffset: PUBLIC PROC [loc1, loc2: Location, break: NAT _ 1, skipCommentNodes: BOOL _ FALSE] RETURNS [count: Offset] = { count _ TextNode.LocOffset[DocLoc[loc1], DocLoc[loc2], break, skipCommentNodes ! TextNode.BadArgs => ERROR BadArgs]; }; BadArgs: PUBLIC ERROR=CODE; <> GetCaret: PUBLIC PROC RETURNS [loc: Location] = { RETURN [MyLoc[TEditOps.CaretLoc[]]] }; CaretBefore: PUBLIC PROC = { TEditSelection.CaretBeforeSelection[] }; CaretAfter: PUBLIC PROC = { TEditSelection.CaretAfterSelection[] }; CaretOnly: PUBLIC PROC = { [] _ TEditInput.MakePointSelection[] }; GoToNextCharacter: PUBLIC PROC [n: INT _ 1] = { TEditInputOps.GoToNextChar[n] }; GoToNextWord: PUBLIC PROC [n: INT _ 1] = { TEditInputOps.GoToNextWord[n] }; GoToNextNode: PUBLIC PROC [n: INT _ 1] = { TEditInputOps.GoToNextNode[n] }; GoToPreviousCharacter: PUBLIC PROC [n: INT _ 1] = { TEditInputOps.GoToPreviousChar[n] }; GoToPreviousWord: PUBLIC PROC [n: INT _ 1] = { TEditInputOps.GoToPreviousWord[n] }; GoToPreviousNode: PUBLIC PROC [n: INT _ 1] = { TEditInputOps.GoToPreviousNode[n] }; <> ToPrimary: PUBLIC PROC = { TEditInputOps.Copy[primary] }; ToSecondary: PUBLIC PROC = { TEditInputOps.Copy[secondary] }; Transpose: PUBLIC PROC = { TEditInputOps.Transpose[] }; <> InsertRope: PUBLIC PROC [rope: ROPE] = { TEditInputOps.InsertRope[rope] }; InsertChar: PUBLIC PROC [char: CHAR] = { TEditInputOps.InsertChar[char] }; InsertLineBreak: PUBLIC PROC = { TEditInputOps.InsertLineBreak[] }; BackSpace: PUBLIC PROC [n: INT _ 1] = { TEditInputOps.BackSpace[n] }; BackWord: PUBLIC PROC [n: INT _ 1] = { TEditInputOps.BackWord[n] }; DeleteNextCharacter: PUBLIC PROC [n: INT _ 1] = { TEditInputOps.DeleteNextChar[n] }; DeleteNextWord: PUBLIC PROC [n: INT _ 1] = { TEditInputOps.DeleteNextWord[n] }; InsertTime: PUBLIC PROC = { TEditInputOps.InsertTime[] }; InsertBrackets: PUBLIC PROC [left, right: CHAR] = { TEditInputOps.InsertBrackets[left, right] }; MakeControlCharacter: PUBLIC PROC = { TEditInputOps.MakeControlCharacter[] }; UnMakeControlCharacter: PUBLIC PROC = { TEditInputOps.UnMakeControlCharacter[] }; MakeOctalCharacter: PUBLIC PROC = { TEditInputOps.MakeOctalCharacter[] }; UnMakeOctalCharacter: PUBLIC PROC = { TEditInputOps.UnMakeOctalCharacter[] }; ExpandAbbreviation: PUBLIC PROC = { TEditInputOps.ExpandAbbreviation[] }; Delete: PUBLIC PROC = { TEditInputOps.Delete[] }; Paste: PUBLIC PROC = { TEditInputOps.Paste[] }; SaveForPaste: PUBLIC PROC = { TEditInputOps.SaveForPaste[] }; SaveSpanForPaste: PUBLIC PROC [startLoc, endLoc: Location, grain: SelectionGrain _ char] = { TEditInputOps.SaveSpanForPaste[DocLoc[startLoc], DocLoc[endLoc], DocGran[grain]] }; <<>> AllLower: PUBLIC PROC = { TEditInputOps.Capitalise[allLower] }; AllCaps: PUBLIC PROC = { TEditInputOps.Capitalise[allCaps] }; InitialCaps: PUBLIC PROC = { TEditInputOps.Capitalise[initCaps] }; FirstCap: PUBLIC PROC = { TEditInputOps.Capitalise[firstCap] }; MesaFormatting: PUBLIC PROC = { [] _ TEditMesaOps.SetMesaLooksOp[] }; Repeat: PUBLIC PROC = { [] _ TEditInput.Repeat[] }; Undo: PUBLIC PROC = { [] _ TEditInput.Cancel[] }; <> Break: PUBLIC PROC = { TEditInputOps.Break[] }; Join: PUBLIC PROC = { TEditInputOps.Join[] }; Nest: PUBLIC PROC = { TEditInputOps.Nest[] }; UnNest: PUBLIC PROC = { TEditInputOps.UnNest[] }; <> SetSelectionLooks: PUBLIC PROC [which: WhichSelection _ primary] = { <> TEditSelection.SetSelLooks[SELECT which FROM primary => TEditSelection.pSel, secondary => TEditSelection.sSel, feedback => TEditSelection.fSel, ENDCASE => ERROR] }; <<>> FetchLooks: PUBLIC PROC [node: Ref, index: Offset] RETURNS [ROPE] = { RETURN [TextLooks.LooksToRope[TextEdit.FetchLooks[TextNode.NarrowToTextNode[node],index]]] }; SetLooks: PUBLIC PROC [looks: ROPE, which: WhichLooks _ selection] = { lks: TextLooks.Looks = TextLooks.RopeToLooks[looks]; IF which=selection THEN TEditInputOps.ChangeLooks[add: lks, remove: TextLooks.allLooks] ELSE TEditInputOps.ChangeCaretLooks[add: lks, remove: TextLooks.allLooks] }; AddLooks: PUBLIC PROC [looks: ROPE, which: WhichLooks _ selection] = { lks: TextLooks.Looks = TextLooks.RopeToLooks[looks]; IF which=selection THEN TEditInputOps.ChangeLooks[add: lks, remove: TextLooks.noLooks] ELSE TEditInputOps.ChangeCaretLooks[add: lks, remove: TextLooks.noLooks] }; SubtractLooks: PUBLIC PROC [looks: ROPE, which: WhichLooks _ selection] = { lks: TextLooks.Looks = TextLooks.RopeToLooks[looks]; IF which=selection THEN TEditInputOps.ChangeLooks[add: TextLooks.noLooks, remove: lks] ELSE TEditInputOps.ChangeCaretLooks[add: TextLooks.noLooks, remove: lks] }; ClearLooks: PUBLIC PROC [which: WhichLooks _ selection] = { IF which=selection THEN TEditInputOps.ChangeLooks[add: TextLooks.noLooks, remove: TextLooks.allLooks] ELSE TEditInputOps.ChangeCaretLooks[add: TextLooks.noLooks, remove: TextLooks.allLooks] }; CopyLooks: PUBLIC PROC = { TEditInputOps.CopyLooks[] }; <> GetFormat: PUBLIC PROC [node: Ref] RETURNS [ROPE] = { name: ATOM = node.formatName; RETURN [IF name=NIL THEN "default" ELSE Atom.GetPName[name]] }; ForEachNode: PROC [which: WhichNodes, proc: PROC [TextNode.Ref]] = { pSel: TEditDocument.Selection = TEditSelection.pSel; SELECT which FROM root => proc[TextNode.Root[pSel.start.pos.node]]; selection => FOR node: TextNode.Ref _ pSel.start.pos.node, TextNode.StepForward[node] DO proc[node]; IF node = pSel.end.pos.node THEN EXIT; ENDLOOP; ENDCASE => ERROR }; SetFormat: PUBLIC PROC [format: ROPE, which: WhichNodes _ selection] = { Set: PROC [ref: TextNode.Ref] = { TEditInputOps.SetFormatName[format, ref] }; ForEachNode[which, Set] }; SetNodeFormat: PUBLIC PROC [format: ROPE, node: Ref] = { root: TextNode.Ref; formatName: ATOM _ IF format.IsEmpty THEN NIL ELSE Atom.MakeAtom[format]; root _ TextNode.Root[node]; [] _ TEditLocks.Lock[root, "TiogaOpsSetNodeFormat"]; TextEdit.ChangeFormat[node, formatName, NIL, root]; TEditLocks.Unlock[root] }; SetNodeStyle: PUBLIC PROC [style: ROPE, node: Ref] = { root: TextNode.Ref _ TextNode.Root[node]; [] _ TEditLocks.Lock[root, "TiogaOpsSetNodeStyle"]; TextEdit.ChangeStyle[node, style, NIL, root]; TEditLocks.Unlock[root] }; CaretNodeFormat: PUBLIC PROC = { TEditInputOps.SetFormat[] }; InsertFormat: PUBLIC PROC = { TEditInputOps.GetFormat[] }; CopyFormat: PUBLIC PROC = { TEditInputOps.CopyFormat[] }; <> GetStyle: PUBLIC PROC [node: Ref] RETURNS [ROPE] = { name: ATOM ~ NodeStyleOps.StyleNameForNode[node]; RETURN [IF name=NIL THEN "default" ELSE Atom.GetPName[name]] }; SetStyle: PUBLIC PROC [style: ROPE, which: WhichNodes _ selection] = { Set: PROC [ref: TextNode.Ref] = { TEditInputOps.SetStyleName[style, ref] }; ForEachNode[which, Set] }; <> IsComment: PUBLIC PROC [node: Ref] RETURNS [BOOL] = { txt: TextNode.RefTextNode = TextNode.NarrowToTextNode[node]; RETURN [txt # NIL AND txt.comment] }; SetComment: PUBLIC PROC = { TEditInputOps.SetCommentProp[TRUE] }; SetNotComment: PUBLIC PROC = { TEditInputOps.SetCommentProp[FALSE] }; <> SetProp: PUBLIC PROC [name: ATOM, value: REF, which: WhichNodes _ selection] = { Put: PROC [node: TextNode.Ref] = { TextEdit.PutProp[node, name, value, TEditInput.CurrentEvent[]] }; ForEachNode[which, Put] }; <> RegisterAbbrevFailedProc: PUBLIC PROC [proc: PROC RETURNS [BOOL]] = { TEditInputOps.RegisterAbbrevFailedProc[proc] }; <<>> RegisterFileNameProc: PUBLIC PROC [ proc: PROC [ROPE, Viewer] RETURNS [fileName: ROPE, search: ROPE] ] = { TEditOps.RegisterFileNameProc[proc] }; <> PutTextKey: PUBLIC PROC [node: Ref, where: Offset, key: REF] = { <> NodeAddrs.PutTextAddr[TextNode.NarrowToTextNode[node], key, where] }; GetTextKey: PUBLIC PROC [node: Ref, key: REF] RETURNS [loc: Location] = { <> where: Offset; n: TextNode.RefTextNode; [n, where] _ NodeAddrs.GetTextAddr[TextNode.NarrowToTextNode[node], key ! NodeAddrs.TextAddrNotFound => GOTO Error]; RETURN [[n, where]]; EXITS Error => ERROR TextKeyNotFound }; TextKeyNotFound: PUBLIC ERROR = CODE; RemoveTextKey: PUBLIC PROC [node: Ref, key: REF] = { NodeAddrs.RemTextAddr[TextNode.NarrowToTextNode[node], key] }; MapTextKeys: PUBLIC PROC [node: Ref, proc: PROC [key: REF, where: Offset] RETURNS [BOOLEAN]] RETURNS [BOOLEAN] = { Do: PROC [addr: REF, location: Offset] RETURNS [BOOLEAN] = { RETURN [proc[addr, location]] }; RETURN [NodeAddrs.MapTextAddrs[TextNode.NarrowToTextNode[node], Do]] }; END.