<> <> <> <> <> <> <> <> <<>> DIRECTORY Ascii USING [Upper], EditSpan USING [ChangeLooks, ChangeNesting, CannotDoEdit, Copy, Delete, InsertTextNode, Merge, Move, MoveOnto, Place, Replace, SaveForPaste, SavedForPaste, Split, Transpose], EditSpanSupport USING [Apply], MessageWindow USING [Append, Blink], Rope USING [ROPE, Substr, Concat], RopeEdit USING [AlphaNumericChar], TEditDisplay USING [EstablishLine], TEditDocument USING [BeforeAfter, Selection, SelectionId, SelectionGrain, SelectionPoint, SelectionRec, TEditDocumentData], TEditImpl USING [CaretAtEnd], TEditInput USING [currentEvent], TEditInputOps USING [GoToPreviousNode, ModifyOp], TEditLocks USING [Access, Lock, LockBoth, LockRef, Unlock], TEditOps USING [], TEditProfile USING [editTypeScripts], TEditRefresh USING [ScrollToEndOfSel], TEditSelection USING [Alloc, CaretVisible, Copy, Free, Deselect, GetSelectionGrain, InsertionPoint, LockBothSelections, LockSel, MakeSelection, MakePointSelection, nilSel, oldSel, pSel, SelectionRoot, sSel, UnlockBothSelections, UnlockDocAndPSel, UnlockSel], TextEdit USING [DeleteText, FetchChar, FetchLooks, FromRope, Size], TextLooks USING [Look, Looks, allLooks, noLooks], TextLooksSupport USING [ModifyLooks], TextNode USING [EndPos, FirstChild, LastLocWithin, Level, Location, MakeNodeLoc, MakeNodeSpan, NarrowToTextNode, NodeItself, nullSpan, Parent, Ref, RefTextNode, Root, Span, StepForward, StepBackward], TypeScript USING [ChangeLooks], ViewerClasses USING [Viewer]; TEditInputOpsImpl: CEDAR PROGRAM IMPORTS Ascii, EditSpan, MessageWindow, EditSpanSupport, Rope, RopeEdit, TextEdit, TextLooksSupport, TextNode, TEditDisplay, TEditImpl, TEditInput, TEditInputOps, TEditLocks, TEditProfile, TEditRefresh, TEditSelection, TypeScript EXPORTS TEditInputOps, TEditOps = BEGIN OPEN TEditDocument, TEditSelection; RefTextNode: TYPE ~ TextNode.RefTextNode; CallWithLocks: PUBLIC PROC [proc: PROC [root: RefTextNode, tSel: Selection], access: TEditLocks.Access _ write] = { <> <> <> root: RefTextNode; tSel: Selection _ NIL; lockRef: TEditLocks.LockRef _ NIL; { ENABLE UNWIND => { UnlockSel[primary]; IF lockRef # NIL THEN TEditLocks.Unlock[root]; IF tSel # NIL THEN Free[tSel]; }; LockSel[primary, "CallWithLocks"]; IF (access=write AND ~CheckReadonly[pSel]) OR (root _ SelectionRoot[pSel])=NIL THEN { UnlockSel[primary]; RETURN }; lockRef _ TEditLocks.Lock[root, "CallWithLocks", access]; tSel _ Alloc[]; TEditSelection.Copy[source: pSel, dest: tSel]; proc[root, tSel]; Free[tSel]; tSel _ NIL; UnlockDocAndPSel[root]; }; }; CallWithBothLocked: PUBLIC PROC [ proc: PROC [sourceRoot, destRoot: RefTextNode, tSel, srcSel, targetSel: Selection], targetSel, srcSel: Selection, sourceAccess: TEditLocks.Access] = { <> <> <> <> <> <> <<[DKW: Note the calls on LockBothSelections and Deselect; they suggest that trouble will ensue unless (targetSel=pSel AND srcSel=sSel) OR (targetSel=sSel AND srcSel=pSel).]>> sourceRoot, destRoot: RefTextNode; tSel, tSel1, tSel2: Selection _ NIL; lockRef: TEditLocks.LockRef _ NIL; Cleanup: PROC = { IF lockRef # NIL THEN { TEditLocks.Unlock[sourceRoot]; TEditLocks.Unlock[destRoot] }; IF tSel # NIL THEN Free[tSel]; IF tSel1 # NIL THEN Free[tSel1]; IF tSel2 # NIL THEN Free[tSel2]; UnlockBothSelections[] }; { ENABLE UNWIND => Cleanup[]; LockBothSelections["CallWithBothLocked"]; IF srcSel.viewer=NIL OR targetSel.viewer=NIL THEN { UnlockBothSelections[]; RETURN }; IF ~CheckReadonly[targetSel] OR -- don't edit a readonly document (sourceAccess=write AND ~CheckReadonly[srcSel]) THEN { Deselect[$secondary]; UnlockBothSelections[]; RETURN }; tSel1 _ Alloc[]; tSel2 _ Alloc[]; tSel _ Alloc[]; TEditSelection.Copy[source: srcSel, dest: tSel1]; TEditSelection.Copy[source: targetSel, dest: tSel2]; TEditSelection.Copy[source: targetSel, dest: tSel]; sourceRoot _ SelectionRoot[srcSel]; destRoot _ SelectionRoot[targetSel]; [lockRef, ----] _ TEditLocks.LockBoth[ sourceRoot, destRoot, "CallWithBothLocked", sourceAccess, write]; Deselect[$secondary]; Deselect[$primary]; proc[sourceRoot: sourceRoot, destRoot: destRoot, tSel: tSel, srcSel: tSel1, targetSel: tSel2]; }; Cleanup[]; }; pdelNode: RefTextNode = TextEdit.FromRope["!"]; DoPendingDelete: PUBLIC PROC = { <> <> PendingDelete: PROC [root: RefTextNode, tSel: Selection] = { pSel.pendingDelete _ TRUE; tSel^ _ nilSel^; tSel.granularity _ char; tSel.looks _ pSel.looks; tSel.start.pos _ tSel.end.pos _ [pdelNode,0]; tSel.pendingDelete _ FALSE; CopyToDoc[targetSel: pSel, srcSel: tSel, target: primary, lock: FALSE]; Delete[saveForPaste: FALSE]; }; CallWithLocks[PendingDelete] }; EditFailed: PUBLIC PROC [msg: Rope.ROPE _ NIL] = { MessageWindow.Append[IF msg=NIL THEN "Can't do it." ELSE msg, TRUE]; MessageWindow.Blink[]; }; CaretLoc: PUBLIC PROC [s: Selection _ NIL] RETURNS [TextNode.Location] = { IF s=NIL THEN s _ pSel; IF GetSelectionGrain[s] = node THEN SELECT s.insertion FROM before => RETURN[[s.start.pos.node, TextNode.NodeItself]]; after => RETURN[[s.end.pos.node, TextNode.NodeItself]]; ENDCASE => ERROR ELSE SELECT s.insertion FROM before => RETURN[s.start.pos]; after => RETURN[[s.end.pos.node, s.end.pos.where+1]]; ENDCASE => ERROR; }; NodeSpanFromSelection: PROC[sel: Selection] RETURNS[span: TextNode.Span] = { RETURN[TextNode.MakeNodeSpan[first: sel.start.pos.node, last: sel.end.pos.node]] }; SelectionSpan: PROC[sel: Selection] RETURNS[span: TextNode.Span] = { IF GetSelectionGrain[sel]=node THEN RETURN[NodeSpanFromSelection[sel]] ELSE RETURN[[start: sel.start.pos, end: sel.end.pos]]; }; Delete: PUBLIC PROC [saveForPaste: BOOL _ TRUE] = { DoDelete: PROC [root: RefTextNode, tSel: Selection] = { Deselect[$primary]; tSel.pendingDelete _ FALSE; SELECT GetSelectionGrain[tSel] FROM point => NULL; node => { -- this is complex because must worry about deleting all of the document newSelNode: RefTextNode _ TextNode.StepForward[tSel.end.pos.node]; newSelText: RefTextNode _ TextNode.NarrowToTextNode[newSelNode]; newSelInsertion: BeforeAfter; IF newSelNode=NIL THEN { -- deleting the last node of the doc newSelNode _ TextNode.StepBackward[tSel.start.pos.node]; IF newSelNode=NIL OR newSelNode=root THEN { -- deleting all the nodes child: RefTextNode _ TextNode.FirstChild[root]; tSel.start.pos.node _ child; newSelNode _ EditSpan.InsertTextNode[root: root, old: child, where: before, inherit: FALSE, event: TEditInput.currentEvent]; }; newSelText _ TextNode.NarrowToTextNode[newSelNode]; newSelInsertion _ IF newSelText#NIL AND TextEdit.Size[newSelText] > 0 THEN after ELSE before } ELSE { newSelInsertion _ before }; EditSpan.Delete[ root: SelectionRoot[tSel], del: NodeSpanFromSelection[tSel], event: TEditInput.currentEvent, saveForPaste: saveForPaste ! EditSpan.CannotDoEdit => GOTO Bad]; IF newSelText # NIL THEN { tSel.start.pos.node _ tSel.end.pos.node _ newSelText; tSel.start.pos.where _ tSel.end.pos.where _ IF newSelInsertion=before THEN 0 ELSE TextEdit.Size[newSelText] -- -1? --; tSel.granularity _ point } ELSE { -- the new selection node is not a text node tSel.start.pos _ tSel.end.pos _ TextNode.MakeNodeLoc[newSelNode]; tSel.granularity _ node }; tSel.insertion _ newSelInsertion; }; ENDCASE => { EditSpan.Delete[ root: SelectionRoot[tSel], del: [tSel.start.pos, tSel.end.pos], event: TEditInput.currentEvent, saveForPaste: saveForPaste ! EditSpan.CannotDoEdit => GOTO Bad]; tSel.end.pos _ tSel.start.pos; tSel.granularity _ point; tSel.insertion _ before; }; MakeSelection[selection: primary, new: tSel]; EXITS Bad => { MakeSelection[selection: primary, new: tSel]; EditFailed[] } }; CallWithLocks[DoDelete]; }; BackSpace: PUBLIC PROC [count: INT _ 1] = { DoBackSpace: PROC [root: RefTextNode, tSel: Selection] = { node: RefTextNode; flush: TextNode.Location _ InsertionPoint[tSel]; IF flush.where=TextNode.NodeItself THEN GOTO Bad; IF (node _ TextNode.NarrowToTextNode[flush.node])=NIL THEN GOTO Bad; IF flush.where=0 THEN { Join[]; RETURN }; -- already at start of node; do a Join IF (count _ MIN[count,flush.where]) <= 0 THEN GOTO Bad; Deselect[$primary]; flush.where _ flush.where-count; TextEdit.DeleteText[root, node, flush.where, count, TEditInput.currentEvent]; MakePointSelection[tSel, flush]; EXITS Bad => EditFailed[] }; IF count > 0 THEN CallWithLocks[DoBackSpace]; }; FindPrevWord: PUBLIC PROC [node: RefTextNode, offset: INT] RETURNS [nChars: CARDINAL] = { Alpha: PROC [index: INT] RETURNS [BOOL] ~ { set: NAT; char: CHAR; [set, char] _ TextEdit.FetchChar[node, index]; RETURN [set=0 AND RopeEdit.AlphaNumericChar[char]]; }; nChars _ 0; WHILE offset>0 AND NOT Alpha[offset-1] DO offset _ offset - 1; nChars _ nChars + 1; ENDLOOP; WHILE offset>0 AND Alpha[offset-1] DO offset _ offset - 1; nChars _ nChars + 1; ENDLOOP; }; BackWord: PUBLIC PROC [count: INT _ 1] = { DoBackWord: PROC [root: RefTextNode, tSel: Selection] = { node: RefTextNode; nChars: CARDINAL _ 0; pos: TextNode.Location _ InsertionPoint[tSel]; IF (node _ TextNode.NarrowToTextNode[pos.node])=NIL THEN GOTO Bad; IF pos.where = TextNode.NodeItself THEN GOTO Bad; Deselect[$primary]; FOR garbage:INT IN [0..count) DO wChars: CARDINAL _ FindPrevWord[node,pos.where]; pos.where _ pos.where - wChars; nChars _ nChars + wChars; ENDLOOP; TextEdit.DeleteText[root, node, pos.where, nChars, TEditInput.currentEvent]; MakePointSelection[tSel, pos]; EXITS Bad => EditFailed[]; }; IF count > 0 THEN CallWithLocks[DoBackWord]; }; GoToPreviousWord: PUBLIC PROC [count: INT _ 1] = { DoGoToPreviousWord: PROC [root: RefTextNode, tSel: Selection] = { pos: TextNode.Location; node: RefTextNode; nChars: CARDINAL; pos _ InsertionPoint[tSel]; FOR garbage:INT IN [0..count) DO IF (node _ TextNode.NarrowToTextNode[pos.node])=NIL OR pos.where=TextNode.NodeItself OR pos.where=0 THEN { TEditInputOps.GoToPreviousNode; RETURN }; nChars _ FindPrevWord[node,pos.where]; pos.where _ pos.where - nChars; ENDLOOP; MakePointSelection[tSel,pos]; }; IF count > 0 THEN CallWithLocks[DoGoToPreviousWord, read]; }; CopyToTypeScript: PROC [targetSel: Selection, source: TextNode.Span] = { TSCopy: PROC [node: RefTextNode, start, len: INT] RETURNS [stop: BOOL] = { rope: Rope.ROPE _ Rope.Substr[node.rope, start, len]; IF node # source.end.node THEN rope _ Rope.Concat[rope, "\n"]; -- add CR's between nodes <<-- shove it in as though typed...>> targetSel.viewer.class.notify[targetSel.viewer, LIST[rope]]; RETURN [FALSE]; }; EditSpanSupport.Apply[source,TSCopy]; }; UnConvertNodeSelects: PROC [sel: Selection] = { SELECT GetSelectionGrain[sel] FROM char => { <> IF sel.end.pos.node=sel.start.pos.node AND sel.end.pos.where { sel.start.pos.where _ 0; sel.end.pos.where _ TextNode.EndPos[sel.end.pos.node]; }; ENDCASE; }; CopyToDoc: PROC [targetSel, srcSel: Selection, target: SelectionId, lock: BOOL _ TRUE] = { DoCopyToDoc: PROC [sourceRoot, destRoot: RefTextNode, tSel, srcSel, targetSel: Selection] = { IF tSel.pendingDelete THEN { -- replace target by source [start: tSel.start.pos, end: tSel.end.pos] _ EditSpan.Replace[ destRoot: destRoot, sourceRoot: sourceRoot, dest: SelectionSpan[tSel], source: SelectionSpan[srcSel], saveForPaste: TRUE, event: TEditInput.currentEvent ! EditSpan.CannotDoEdit => GOTO Bad].result; tSel.pendingDelete _ FALSE; } ELSE { -- source goes to target caret loc: TextNode.Location _ InsertionPoint[tSel]; unnest: INTEGER _ 0; -- amount to unnest by if copying nodes after where: EditSpan.Place _ IF tSel.insertion = before THEN before ELSE after; IF GetSelectionGrain[srcSel]=node THEN { -- don't copy nodes into target node IF where=before AND loc.where>0 AND loc.where=TextEdit.Size[TextNode.NarrowToTextNode[loc.node]] THEN where _ after; -- caret at end of node, so copy after loc.where _ TextNode.NodeItself; IF where=after AND tSel.start.pos.node#tSel.end.pos.node THEN unnest _ TextNode.Level[tSel.end.pos.node]-TextNode.Level[tSel.start.pos.node]; }; [start: tSel.start.pos, end: tSel.end.pos] _ EditSpan.Copy[ destRoot: destRoot, sourceRoot: sourceRoot, dest: loc, source: SelectionSpan[srcSel], where: where, nesting: 0, event: TEditInput.currentEvent ! EditSpan.CannotDoEdit => GOTO Bad].result; IF unnest > 0 THEN -- unnest so that copied span starts at same level as start of dest span [start: tSel.start.pos, end: tSel.end.pos] _ EditSpan.ChangeNesting[ root: destRoot, span: [tSel.start.pos, tSel.end.pos], change: -unnest, event: TEditInput.currentEvent ! EditSpan.CannotDoEdit => CONTINUE].new; }; tSel.granularity _ srcSel.granularity; UnConvertNodeSelects[tSel]; tSel.insertion _ after; MakeSelection[selection: primary, new: tSel]; TEditSelection.Copy[source: tSel, dest: oldSel]; -- save the copied selection for Repeat's EXITS Bad => { MakeSelection[selection: primary, new: IF target=primary THEN targetSel ELSE srcSel]; EditFailed[] } }; IF lock THEN CallWithBothLocked[DoCopyToDoc, targetSel, srcSel, read] ELSE { -- special hack for DoPendingDelete. tSel: Selection _ Alloc[]; TEditSelection.Copy[source: targetSel, dest: tSel]; DoCopyToDoc[SelectionRoot[srcSel], SelectionRoot[targetSel], tSel, srcSel, targetSel]; Free[tSel]; }; }; Copy: PUBLIC PROC [target: SelectionId _ primary] = { ENABLE UNWIND => UnlockBothSelections[]; targetSel: Selection _ IF target=primary THEN pSel ELSE sSel; srcSel: Selection _ IF target=primary THEN sSel ELSE pSel; LockBothSelections["Copy"]; IF srcSel.viewer#NIL AND targetSel.viewer#NIL THEN DoCopy[target, targetSel, srcSel]; UnlockBothSelections[]; }; CheckReadonly: PUBLIC PROC [targetSel: Selection] RETURNS [BOOL] = { IF targetSel.viewer=NIL OR NOT targetSel.data.readOnly THEN RETURN [TRUE]; EditFailed["Cannot modify read only document."]; RETURN [FALSE]; }; DoCopy: PROC [target: SelectionId, targetSel, srcSel: Selection] = { IF ~CheckReadonly[targetSel] THEN { Deselect[$secondary]; RETURN }; IF targetSel.data.tsInfo#NIL AND (~TEditProfile.editTypeScripts OR TEditImpl.CaretAtEnd[targetSel]) THEN { <> tSel: Selection _ Alloc[]; tSel1: Selection _ Alloc[]; span: TextNode.Span _ [srcSel.start.pos, srcSel.end.pos]; TEditSelection.Copy[source: sSel, dest: oldSel]; -- save the secondary selection for Repeat's <<-- force selection to end of typescript>> TEditSelection.Copy[source: targetSel, dest: tSel]; tSel.insertion _ before; tSel.granularity _ point; tSel.start.pos _ tSel.end.pos _ TextNode.LastLocWithin[tSel.data.text]; TEditSelection.Copy[source: srcSel, dest: tSel1]; srcSel _ tSel1; Deselect[$primary]; Deselect[$secondary]; CopyToTypeScript[tSel, span]; IF target=primary THEN { -- leave primary as caret at end of typescript tSel.start.pos _ tSel.end.pos _ TextNode.LastLocWithin[tSel.data.text]; MakeSelection[selection: primary, new: tSel]; } ELSE MakeSelection[selection: primary, new: srcSel]; Free[tSel]; Free[tSel1]; } ELSE IF srcSel.pendingDelete THEN Move[target] ELSE CopyToDoc[targetSel, srcSel, target]; }; Paste: PUBLIC PROC = { DoPaste: PROC [root: RefTextNode, tSel: Selection] = { source: TextNode.Span _ EditSpan.SavedForPaste[]; tdd: TEditDocument.TEditDocumentData _ tSel.data; IF source = TextNode.nullSpan OR tSel.viewer=NIL THEN GOTO Bad; IF tdd.tsInfo=NIL AND tSel.pendingDelete THEN { DoPendingDelete[]; TEditSelection.Copy[source: pSel, dest: tSel] }; Deselect[$secondary]; <<-- now create a phony secondary selection for Copy>> tSel.start.pos _ source.start; tSel.end.pos _ source.end; IF source.start.where=TextNode.NodeItself OR source.end.where=TextNode.NodeItself THEN { tSel.granularity _ node; UnConvertNodeSelects[tSel] } ELSE tSel.granularity _ char; tSel.viewer _ pSel.viewer; -- else Copy will think there isn't a secondary selection tSel.data _ NIL; tSel.pendingDelete _ FALSE; DoCopy[primary, pSel, tSel]; EXITS Bad => EditFailed[]; }; CallWithLocks[DoPaste]; }; Move: PUBLIC PROC [target: SelectionId _ primary] = { targetSel: Selection _ IF target=primary THEN pSel ELSE sSel; srcSel: Selection _ IF target=primary THEN sSel ELSE pSel; DoMove: PROC [sourceRoot, destRoot: RefTextNode, tSel, srcSel, targetSel: Selection] = { pDel: BOOL; srcGrain: TEditDocument.SelectionGrain ~ GetSelectionGrain[srcSel]; IF srcGrain=node THEN { -- see if moving entire contents newSelNode: RefTextNode _ TextNode.StepForward[srcSel.end.pos.node]; IF newSelNode=NIL THEN { -- moving the last node of the doc newSelNode _ TextNode.StepBackward[srcSel.start.pos.node]; IF (newSelNode=NIL OR newSelNode=sourceRoot) AND destRoot # sourceRoot THEN { <<-- moving all the nodes to different tree>> child: RefTextNode _ TextNode.FirstChild[sourceRoot]; srcSel.start.pos.node _ child; -- make sure doesn't include the root [] _ EditSpan.InsertTextNode[ root: sourceRoot, old: child, where: before, inherit: FALSE, event: TEditInput.currentEvent]; }; }; }; IF (pDel _ tSel.pendingDelete) THEN { -- move source onto target IF srcGrain=point THEN { EditSpan.Delete[ root: destRoot, del: SelectionSpan[tSel], event: TEditInput.currentEvent, saveForPaste: TRUE ! EditSpan.CannotDoEdit => GOTO Bad]; tSel.end.pos _ tSel.start.pos; tSel.granularity _ point; } ELSE { [start: tSel.start.pos, end: tSel.end.pos] _ EditSpan.MoveOnto[ destRoot: destRoot, sourceRoot: sourceRoot, dest: SelectionSpan[tSel], source: SelectionSpan[srcSel], saveForPaste: TRUE, event: TEditInput.currentEvent ! EditSpan.CannotDoEdit => GOTO Bad].result; }; tSel.pendingDelete _ FALSE; } ELSE { -- move source to target caret loc: TextNode.Location _ InsertionPoint[tSel]; where: EditSpan.Place _ (IF tSel.insertion = before THEN before ELSE after); unnest: INTEGER _ 0; -- amount to unnest by if moving nodes after IF srcGrain=node THEN { -- don't move nodes into target node IF where = before AND loc.where > 0 AND loc.where = TextEdit.Size[TextNode.NarrowToTextNode[loc.node]] THEN where _ after; -- caret at end of node, so move after loc.where _ TextNode.NodeItself; IF where=after AND tSel.start.pos.node#tSel.end.pos.node THEN unnest _ TextNode.Level[tSel.end.pos.node]-TextNode.Level[tSel.start.pos.node]; }; IF srcGrain=point THEN { tSel.end.pos _ tSel.start.pos _ loc; tSel.granularity _ point; } ELSE { [start: tSel.start.pos, end: tSel.end.pos] _ EditSpan.Move[ destRoot: destRoot, sourceRoot: sourceRoot, dest: loc, source: SelectionSpan[srcSel], where: where, nesting: 0, event: TEditInput.currentEvent ! EditSpan.CannotDoEdit => GOTO Bad].result; }; IF unnest > 0 THEN -- unnest so that moved span starts at same level as start of dest span [start: tSel.start.pos, end: tSel.end.pos] _ EditSpan.ChangeNesting[ root: destRoot, span: [tSel.start.pos, tSel.end.pos], change: -unnest, event: TEditInput.currentEvent ! EditSpan.CannotDoEdit => CONTINUE].new; }; tSel.granularity _ srcSel.granularity; UnConvertNodeSelects[tSel]; tSel.insertion _ after; TEditSelection.Copy[source: tSel, dest: oldSel]; -- save for Repeat's oldSel.pendingDelete _ (target=primary) OR (target=secondary AND pDel); tSel.pendingDelete _ FALSE; MakeSelection[selection: primary, new: tSel]; EXITS Bad => { MakeSelection[selection: primary, new: tSel]; EditFailed[] } }; CallWithBothLocked[DoMove, targetSel, srcSel, write]; }; Transpose: PUBLIC PROC [target: SelectionId _ primary] = { targetSel: Selection _ IF target=primary THEN pSel ELSE sSel; srcSel: Selection _ IF target=primary THEN sSel ELSE pSel; DoTranspose: PROC [sourceRoot, destRoot: RefTextNode, tSel, srcSel, targetSel: Selection] = { [start: srcSel.start.pos, end: srcSel.end.pos] _ EditSpan.Transpose[ alphaRoot: destRoot, betaRoot: sourceRoot, alpha: SelectionSpan[targetSel], beta: SelectionSpan[srcSel], event: TEditInput.currentEvent ! EditSpan.CannotDoEdit => GOTO Bad].newAlpha; UnConvertNodeSelects[srcSel]; srcSel.pendingDelete _ FALSE; MakeSelection[selection: primary, new: srcSel]; TEditSelection.Copy[source: srcSel, dest: oldSel]; -- save for Repeat's EXITS Bad => { MakeSelection[selection: primary, new: IF target=primary THEN targetSel ELSE srcSel]; EditFailed[] } }; CallWithBothLocked[DoTranspose, targetSel, srcSel, write]; }; Break: PUBLIC PROC = { DoBreak: PROC [root: RefTextNode, tSel: Selection] = { null: BOOL _ FALSE; caret: TextNode.Location; newNode: RefTextNode; IF tSel.pendingDelete THEN { DoPendingDelete[]; TEditSelection.Copy[source: pSel, dest: tSel] }; caret _ InsertionPoint[pSel]; Deselect[$primary]; newNode _ TextNode.NarrowToTextNode[EditSpan.Split[root, caret, TEditInput.currentEvent ! EditSpan.CannotDoEdit => GOTO Bad]]; IF newNode # NIL AND TextEdit.Size[TextNode.NarrowToTextNode[caret.node]]=0 AND TextEdit.Size[newNode] > 0 THEN null _ TRUE -- caret was at front of nonempty node ELSE tSel.start.pos.node _ newNode; -- move caret to start of new node tSel.start.pos.where _ 0; tSel.end.pos _ tSel.start.pos; tSel.granularity _ point; tSel.insertion _ before; tSel.pendingDelete _ FALSE; tSel.looks _ TextLooks.noLooks; MakeSelection[selection: primary, new: tSel]; CheckStartLine[viewer: tSel.viewer, old: caret, new: [newNode,0], null: null]; EXITS Bad => { MakeSelection[selection: primary, new: tSel]; EditFailed[] } }; CallWithLocks[DoBreak]; }; Join: PUBLIC PROC = { DoJoin: PROC [root: RefTextNode, tSel: Selection] = { loc: TextNode.Location; pred: RefTextNode; node: RefTextNode _ TextNode.NarrowToTextNode[InsertionPoint[].node]; IF node=NIL OR (pred _ TextNode.StepBackward[node])=NIL OR TextNode.Parent[pred]=NIL --i.e., pred is root-- THEN { EditFailed[]; RETURN }; Deselect[$primary]; loc _ EditSpan.Merge[root, node, TEditInput.currentEvent ! EditSpan.CannotDoEdit => GOTO Bad]; MakePointSelection[tSel, loc]; CheckStartLine[viewer: tSel.viewer, old: [node,0], new: loc]; EXITS Bad => { MakeSelection[selection: primary, new: tSel]; EditFailed[] } }; CallWithLocks[DoJoin]; }; CheckStartLine: PROC [viewer: ViewerClasses.Viewer, old, new: TextNode.Location, null: BOOL _ FALSE] = { FOR v: ViewerClasses.Viewer _ viewer, v.link UNTIL v=NIL DO WITH v.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { vloc: TextNode.Location _ tdd.lineTable.lines[0].pos; IF vloc.node = old.node AND vloc.where >= old.where THEN { vnew: TextNode.Location _ [new.node, new.where+vloc.where-old.where]; IF null AND vnew.where = 0 THEN vnew.node _ old.node; TEditDisplay.EstablishLine[tdd, vnew]; }; }; ENDCASE; IF v.link = viewer THEN EXIT; ENDLOOP; }; SaveForPaste: PUBLIC PROC = { DoSaveForPaste: PROC [root: RefTextNode, tSel: Selection] = { EditSpan.SaveForPaste[SelectionSpan[tSel], TEditInput.currentEvent]; }; CallWithLocks[DoSaveForPaste, read]; }; SaveSpanForPaste: PUBLIC PROC [ startLoc, endLoc: TextNode.Location, grain: TEditDocument.SelectionGrain] = { root: RefTextNode = TextNode.Root[startLoc.node]; lockRef: TEditLocks.LockRef _ NIL; { ENABLE UNWIND => { IF lockRef # NIL THEN TEditLocks.Unlock[root] }; lockRef _ TEditLocks.Lock[root, "SaveSpanForPaste", read]; IF grain=node OR grain=branch THEN { startLoc.where _ TextNode.NodeItself; endLoc.where _ TextNode.NodeItself }; EditSpan.SaveForPaste[[startLoc, endLoc], TEditInput.currentEvent]; TEditLocks.Unlock[root]; }; }; Nest: PUBLIC PROC = { ChangeNesting[1] }; <<-- move the selection to a deeper nesting level in the tree>> UnNest: PUBLIC PROC = { ChangeNesting[-1] }; <<-- move the selection to a shallower nesting level in the tree>> ChangeNesting: PROC [change: INTEGER] = { DoChangeNesting: PROC [root: RefTextNode, tSel: Selection] = { Deselect[$primary]; [] _ EditSpan.ChangeNesting[root: root, span: TextNode.MakeNodeSpan[tSel.start.pos.node, tSel.end.pos.node], change: change, event: TEditInput.currentEvent ! EditSpan.CannotDoEdit => GOTO Bad]; tSel.pendingDelete _ FALSE; MakeSelection[selection: primary, new: tSel]; IF CaretVisible[] THEN TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE]; EXITS Bad => { MakeSelection[selection: primary, new: tSel]; EditFailed[] } }; CallWithLocks[DoChangeNesting]; }; ModifyLook: PUBLIC PROC [look: TextLooks.Look, op: TEditInputOps.ModifyOp] = { DoModifyLook: PROC [root: RefTextNode, tSel: Selection] = { remLooks, addLooks: TextLooks.Looks _ TextLooks.noLooks; IF tSel.granularity#point THEN { Deselect[$primary]; IF op=add THEN addLooks[look] _ TRUE ELSE remLooks[look] _ TRUE; EditSpan.ChangeLooks[root: root, span: [tSel.start.pos, tSel.end.pos], remove: remLooks, add: addLooks, event: TEditInput.currentEvent]; tSel.pendingDelete _ FALSE; MakeSelection[selection: primary, new: tSel]; }; ModifyCaretLook[look, op]; }; CallWithLocks[DoModifyLook]; }; ModifyCaretLook: PUBLIC PROC [look: TextLooks.Look, op: TEditInputOps.ModifyOp] = { LockSel[primary, "ModifyCaretLook"]; pSel.looks[look] _ (op=add); IF SelAtEndOfTypeScript[pSel] THEN SELECT op FROM add => TypeScript.ChangeLooks[pSel.viewer, look]; remove => TypeScript.ChangeLooks[pSel.viewer, Ascii.Upper[look]]; ENDCASE => ERROR; UnlockSel[primary]; }; GetSelLooks: PROC [sel: Selection] RETURNS [looks: TextLooks.Looks] = { first: BOOL _ TRUE; GetSelLooks: PROC [node: RefTextNode, start, len: INT] RETURNS [stop: BOOL] = { end: INT _ MIN[TextEdit.Size[node],start+len]; FOR i: INT IN [start..end) DO lks: TextLooks.Looks _ TextEdit.FetchLooks[node,i]; IF first THEN { first _ FALSE; looks _ lks } ELSE IF lks#looks THEN { OPEN MessageWindow; Append["Selection does not have uniform looks.",TRUE]; Append[" Using looks from first char."]; Blink[]; RETURN [TRUE] }; ENDLOOP; RETURN [FALSE]; }; EditSpanSupport.Apply[span: [sel.start.pos, sel.end.pos], proc: GetSelLooks]; IF first THEN looks _ sel.looks; -- null selection, use caret }; ChangeLooks: PUBLIC PROC [add, remove: TextLooks.Looks] = { DoChangeLooks: PROC [root: RefTextNode, tSel: Selection] = { Deselect[$primary]; ChangeSelLooks[add, remove, tSel]; MakeSelection[tSel, primary]; }; CallWithLocks[DoChangeLooks]; }; CopyLooks: PUBLIC PROC [target: SelectionId _ primary] = { targetSel: Selection _ IF target=primary THEN pSel ELSE sSel; srcSel: Selection _ IF target=primary THEN sSel ELSE pSel; DoCopyLooks: PROC [sourceRoot, destRoot: RefTextNode, tSel, srcSel, targetSel: Selection] = { TEditSelection.Copy[source: srcSel, dest: oldSel]; -- save the secondary selection for Repeat's ChangeSelLooks[add: GetSelLooks[srcSel], remove: TextLooks.allLooks, targetSel: targetSel]; MakeSelection[IF target=primary THEN targetSel ELSE srcSel, primary]; }; CallWithBothLocked[DoCopyLooks, targetSel, srcSel, read]; }; TransposeLooks: PUBLIC PROC [target: SelectionId _ primary] = { <<-- Transpose the looks of the primary and secondary selections>> targetSel: Selection _ IF target=primary THEN pSel ELSE sSel; srcSel: Selection _ IF target=primary THEN sSel ELSE pSel; DoTransLooks: PROC [sourceRoot, destRoot: RefTextNode, tSel, srcSel, targetSel: Selection] = { srcLooks, targetLooks: TextLooks.Looks; srcLooks _ GetSelLooks[srcSel]; targetLooks _ GetSelLooks[targetSel]; TEditSelection.Copy[source: srcSel, dest: oldSel]; -- save for Repeat's Deselect[$primary]; Deselect[$secondary]; ChangeSelLooks[add: srcLooks, remove: targetLooks, targetSel: targetSel]; ChangeSelLooks[add: targetLooks, remove: srcLooks, targetSel: srcSel]; MakeSelection[IF target=primary THEN targetSel ELSE srcSel, primary]; }; CallWithBothLocked[DoTransLooks, targetSel, srcSel, write]; }; ChangeSelLooks: PROC [add, remove: TextLooks.Looks, targetSel: Selection] = { IF targetSel.granularity # point THEN { EditSpan.ChangeLooks[ root: SelectionRoot[targetSel], span: [targetSel.start.pos, targetSel.end.pos], remove: remove, add: add, event: TEditInput.currentEvent]; targetSel.pendingDelete _ FALSE; }; targetSel.looks _ TextLooksSupport.ModifyLooks[targetSel.looks, remove, add]; AdjustTypeScriptLooks[targetSel, add, remove]; }; ChangeCaretLooks: PUBLIC PROC [add, remove: TextLooks.Looks] = { LockSel[primary, "ChangeCaretLooks"]; pSel.looks _ TextLooksSupport.ModifyLooks[pSel.looks, remove, add]; AdjustTypeScriptLooks[pSel, add, remove]; UnlockSel[primary]; }; SelAtEndOfTypeScript: PROC [targetSel: Selection] RETURNS [BOOL] = { tdd: TEditDocumentData; IF targetSel.viewer=NIL THEN RETURN [FALSE]; tdd _ NARROW[targetSel.viewer.data]; IF tdd = NIL OR tdd.tsInfo=NIL THEN RETURN [FALSE]; -- not a typescript IF ~TEditImpl.CaretAtEnd[targetSel] THEN RETURN [FALSE]; RETURN [TRUE]; }; AdjustTypeScriptLooks: PROC [targetSel: Selection, add, remove: TextLooks.Looks] = { IF ~SelAtEndOfTypeScript[targetSel] THEN RETURN; FOR c: CHAR IN TextLooks.Look DO IF remove[c] THEN TypeScript.ChangeLooks[targetSel.viewer, Ascii.Upper[c]]; IF add[c] THEN TypeScript.ChangeLooks[targetSel.viewer, c]; ENDLOOP; }; END.