<> <> <> <> <<>> DIRECTORY BasicTime USING [Now], IO USING [PutR], Rope USING [Fetch, Index, ROPE, Size], RopeEdit USING [AlphaNumericChar, BlankChar, Concat, Substr], RopeReader USING [Create, Get, GetIndex, Backwards, ReadOffEnd, Ref, SetPosition], TextEdit USING [DeleteText, FetchChar, FetchLooks, InsertChar, InsertRope, Offset, Ref, RefTextNode, ReplaceByChar, Size], TextLooks USING [Look, Looks, noLooks], TextNode USING [Backward, FirstChild, LastLocWithin, Location, NarrowToTextNode, NodeItself, Offset, Parent, Ref, RefTextNode, StepForward, StepBackward, TypeName], TEditDocument USING [BeforeAfter, Selection, SelectionPoint], TEditInput USING [CloseEvent, currentEvent], TEditInputOps, TEditOps, TEditRefresh USING [ScrollToEndOfSel], TEditSelection USING [CaretVisible, Copy, Deselect, InsertionPoint, LockSel, MakePointSelection, MakeSelection, pSel, SetSelLooks, UnlockSel], TreeFind USING [Finder, CreateFromRope, Try, TryBackwards], ViewerClasses USING [Viewer], ViewerTools USING [SetSelection]; TEditMiscOpsImpl: CEDAR PROGRAM IMPORTS BasicTime, IO, Rope, RopeEdit, RopeReader, TextEdit, TextNode, TEditInput, TEditInputOps, TEditOps, TEditRefresh, TEditSelection, TreeFind, ViewerTools EXPORTS TEditInputOps = BEGIN OPEN TEditDocument, TEditSelection, TEditOps, TEditInputOps; InsertChar: PUBLIC PROC [char: CHARACTER] = { DoInsertChar: PROC [root: TextEdit.Ref, tSel: Selection] = { pos: TextNode.Location; looks: TextLooks.Looks _ tSel.looks; IF tSel.pendingDelete THEN { DoPendingDelete[]; TEditSelection.Copy[source: pSel, dest: tSel] }; pos _ InsertionPoint[pSel]; -- need to get insertion point after pending delete Deselect[primary]; [] _ TextEdit.InsertChar[root: root, dest: TextNode.NarrowToTextNode[pos.node], char: char, destLoc: pos.where, inherit: FALSE, looks: looks, event: TEditInput.currentEvent]; pos.where _ pos.where+1; MakePointSelection[tSel, pos]; IF CaretVisible[] THEN TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE] }; CallWithLocks[DoInsertChar] }; InsertRope: PUBLIC PROC [rope: Rope.ROPE] = { DoInsertRope: PROC [root: TextEdit.Ref, tSel: Selection] = { pos: TextNode.Location; len: TextNode.Offset _ Rope.Size[rope]; looks: TextLooks.Looks; IF tSel.pendingDelete THEN { DoPendingDelete[]; TEditSelection.Copy[source: pSel, dest: tSel] }; pos _ InsertionPoint[pSel]; -- need to get insertion point after pending delete looks _ tSel.looks; Deselect[primary]; [] _ TextEdit.InsertRope[root: root, dest: TextNode.NarrowToTextNode[pos.node], rope: rope, destLoc: pos.where, inherit: FALSE, looks: looks, event: TEditInput.currentEvent]; pos.where _ pos.where+len; MakePointSelection[tSel, pos]; IF CaretVisible[] THEN TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE] }; CallWithLocks[DoInsertRope] }; InsertLineBreak: PUBLIC PROC = { -- copy leading blanks from previous line DoInsertLineBreak: PROC [root: TextEdit.Ref, tSel: Selection] = { pos: TextNode.Location; node: TextNode.RefTextNode; start, end: TextNode.Offset; rope: Rope.ROPE; IF tSel.pendingDelete THEN { DoPendingDelete[]; TEditSelection.Copy[source: pSel, dest: tSel] }; pos _ InsertionPoint[pSel]; -- need to get insertion point after pending delete IF (node _ TextNode.NarrowToTextNode[pos.node]) = NIL THEN { EditFailed[]; RETURN }; rope _ node.rope; start _ MAX[0, pos.where]; start _ MIN[start, Rope.Size[rope]]; WHILE start > 0 AND Rope.Fetch[rope,start-1] # 15C DO start _ start-1; ENDLOOP; end _ start; WHILE end < pos.where AND RopeEdit.BlankChar[Rope.Fetch[rope,end]] DO end _ end+1; ENDLOOP; InsertRope[RopeEdit.Concat["\n",RopeEdit.Substr[rope,start,end-start]]] }; CallWithLocks[DoInsertLineBreak] }; DeleteNextChar: PUBLIC PROC [count: INT _ 1] = { DoDeleteNextChar: PROC [root: TextEdit.Ref, tSel: Selection] = { flush: TextNode.Location _ InsertionPoint[tSel]; node: TextNode.RefTextNode; IF (node _ TextNode.NarrowToTextNode[flush.node]) = NIL THEN GOTO Bad; IF flush.where=TextNode.NodeItself THEN GOTO Bad; IF (count _ MIN[count,TextEdit.Size[node]-flush.where]) <= 0 THEN GOTO Bad; Deselect[primary]; TextEdit.DeleteText[root, node, flush.where, count, TEditInput.currentEvent]; MakePointSelection[tSel,flush]; TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE]; EXITS Bad => EditFailed[] }; IF count > 0 THEN CallWithLocks[DoDeleteNextChar] }; GoToNextChar: PUBLIC PROC [count: INT _ 1] = { DoGoToNextChar: PROC [root: TextEdit.Ref, tSel: Selection] = { loc: TextNode.Location _ InsertionPoint[tSel]; node: TextNode.RefTextNode; IF (node _ TextNode.NarrowToTextNode[loc.node]) = NIL OR loc.where=TextNode.NodeItself OR (count _ MIN[count,TextEdit.Size[node]-loc.where]) <= 0 THEN { -- try next node GoToNextNode; RETURN }; MakePointSelection[tSel,[node,loc.where+count]]; TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE] }; IF count > 0 THEN CallWithLocks[DoGoToNextChar, read] }; GoToPreviousChar: PUBLIC PROC [count: INT _ 1] = { DoGoToPreviousChar: PROC [root: TextEdit.Ref, tSel: Selection] = { loc: TextNode.Location _ InsertionPoint[tSel]; node: TextNode.RefTextNode; IF (node _ TextNode.NarrowToTextNode[loc.node]) = NIL OR loc.where=TextNode.NodeItself OR loc.where < count THEN { GoToPreviousNode; RETURN }; -- try previous node MakePointSelection[tSel,[node,loc.where-count]]; TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE] }; IF count > 0 THEN CallWithLocks[DoGoToPreviousChar, read] }; FindNextWord: PUBLIC PROC [node: TextNode.RefTextNode, start: TextNode.Offset] RETURNS [nChars: CARDINAL] = { offset: TextNode.Offset _ start; size: TextNode.Offset _ TextEdit.Size[node]; nChars _ 1; WHILE offset EditFailed[] }; IF count > 0 THEN CallWithLocks[DoDeleteNextWord] }; GoToNextWord: PUBLIC PROC [count: INT _ 1] = { DoGoToNextWord: PROC [root: TextEdit.Ref, tSel: Selection] = { pos: TextNode.Location _ InsertionPoint[tSel]; node: TextNode.RefTextNode; size, next: TextEdit.Offset; IF (node _ TextNode.NarrowToTextNode[pos.node])=NIL OR (next _ pos.where)=TextNode.NodeItself THEN { -- try next node GoToNextNode; RETURN }; size _ TextEdit.Size[node]; FOR garbage:INT IN [0..count) DO IF next >= size THEN { GoToNextNode; RETURN }; next _ next+FindNextWord[node,next]; ENDLOOP; MakePointSelection[tSel,[node,next]]; TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE] }; IF count > 0 THEN CallWithLocks[DoGoToNextWord, read] }; GoToNextNode: PUBLIC PROC [count: INT _ 1] = { DoGoToNextNode: PROC [root: TextEdit.Ref, tSel: Selection] = { pos: TextNode.Location _ InsertionPoint[tSel]; node: TextNode.Ref _ pos.node; FOR garbage:INT IN [0..count) DO IF (node _ TextNode.StepForward[node])=NIL THEN { EditFailed[]; RETURN }; ENDLOOP; MakePointSelection[tSel,[node,0]]; TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE] }; IF count > 0 THEN CallWithLocks[DoGoToNextNode, read] }; GoToPreviousNode: PUBLIC PROC [count: INT _ 1] = { DoGoToPreviousNode: PROC [root: TextEdit.Ref, tSel: Selection] = { text: TextNode.RefTextNode; pos: TextNode.Location _ InsertionPoint[tSel]; node: TextNode.Ref _ pos.node; FOR garbage:INT IN [0..count) DO IF (node _ TextNode.StepBackward[node])=NIL OR TextNode.Parent[node]=NIL THEN GOTO Bad; ENDLOOP; IF (text _ TextNode.NarrowToTextNode[node])=NIL THEN GOTO Bad; MakePointSelection[tSel,[text,TextEdit.Size[text]]]; TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE]; EXITS Bad => EditFailed[] }; IF count > 0 THEN CallWithLocks[DoGoToPreviousNode, read] }; AppendDecimal: PROC [text: REF TEXT, number: NAT] = { <> pos: NAT _ text.length; skipZeros: BOOL _ TRUE; div: NAT _ 1000; DO c: CHAR _ '0 + (number / div); number _ number MOD div; IF c # '0 THEN skipZeros _ FALSE; IF NOT skipZeros THEN {text[pos] _ c; pos _ pos + 1}; IF div = 10 THEN EXIT; div _ div / 10; ENDLOOP; text[pos] _ '0 + (number / div); text.length _ pos + 1; }; InsertTime: PUBLIC PROC = { DoInsertTime: PROC [root: TextEdit.Ref, tSel: Selection] = { resLen: TextEdit.Offset; caret: TextNode.Location; dest: TextNode.RefTextNode; looks: TextLooks.Looks _ pSel.looks; date: Rope.ROPE _ IO.PutR[[time[BasicTime.Now[]]]]; dateLen: TextEdit.Offset _ Rope.Index[date, 0, ", "]; IF dateLen < date.Size[] THEN dateLen _ Rope.Index[date, dateLen+2, " "]; IF pSel.pendingDelete THEN DoPendingDelete[]; caret _ InsertionPoint[pSel]; IF (dest _ TextNode.NarrowToTextNode[caret.node])=NIL THEN GOTO Bad; Deselect[primary]; [----, resLen] _ TextEdit.InsertRope[ root: root, dest: dest, rope: date, destLoc: caret.where, inherit: FALSE, looks: looks, event: TEditInput.currentEvent]; tSel.end.pos.node _ tSel.start.pos.node _ caret.node; tSel.end.pos.where _ caret.where+resLen-1; tSel.start.pos.where _ caret.where+dateLen; tSel.insertion _ after; tSel.granularity _ char; tSel.pendingDelete _ FALSE; MakeSelection[selection: primary, new: tSel]; EXITS Bad => EditFailed[] }; CallWithLocks[DoInsertTime] }; InsertBrackets: PUBLIC PROC [left, right: CHAR] = { <<-- insert left char at start of selection, right char after selection>> DoInsertBrackets: PROC [root: TextEdit.Ref, tSel: Selection] = { leftEnd, rightEnd: TextNode.Location; leftNode, rightNode: TextNode.RefTextNode; l, r: TextNode.Offset; leftEnd _ tSel.start.pos; rightEnd _ tSel.end.pos; IF (leftNode _ TextNode.NarrowToTextNode[leftEnd.node])=NIL THEN GOTO Bad; IF (rightNode _ TextNode.NarrowToTextNode[rightEnd.node])=NIL THEN GOTO Bad; IF (r _ rightEnd.where) = TextNode.NodeItself THEN r _ TextEdit.Size[rightNode] ELSE IF tSel.granularity # point THEN r _ r+1; IF (l _ leftEnd.where) = TextNode.NodeItself THEN l _ 0; Deselect[primary]; [----, ----] _ TextEdit.InsertChar[ root: root, dest: rightNode, char: right, destLoc: r, inherit: FALSE, looks: tSel.looks, event: TEditInput.currentEvent]; [----, ----] _ TextEdit.InsertChar[ root: root, dest: leftNode, char: left, destLoc: l, inherit: FALSE, looks: tSel.looks, event: TEditInput.currentEvent]; tSel.start.pos.where _ l+1; IF tSel.granularity = point THEN tSel.end.pos.where _ l+1 ELSE { tSel.granularity _ char; IF leftNode=rightNode THEN tSel.end.pos.where _ r}; tSel.pendingDelete _ FALSE; MakeSelection[selection: primary, new: tSel]; EXITS Bad => EditFailed[] }; CallWithLocks[DoInsertBrackets] }; End: ERROR = CODE; -- private; for use in SelectMatchingBrackets SelectMatchingBrackets: PUBLIC PROC [left, right: CHAR] = { IF ~DoSelectMatchingBrackets[left, right] THEN EditFailed["No match."] }; DoSelectMatchingBrackets: PUBLIC PROC [left, right: CHAR] RETURNS [found: BOOL] = { <<-- extend selection until includes matching left and right brackets>> rdr: RopeReader.Ref _ RopeReader.Create[]; ref, parent: TextNode.Ref; node: TextNode.RefTextNode; GetPreviousNode: PROC RETURNS [n: TextNode.RefTextNode] = { DO -- search for previous text node [ref,parent,] _ TextNode.Backward[ref,parent]; IF ref=NIL THEN ERROR End; IF (n _ TextNode.NarrowToTextNode[ref]) # NIL THEN RETURN [n]; ENDLOOP }; GetLeftChar: PROC RETURNS [CHAR] = { RETURN [RopeReader.Backwards[rdr ! RopeReader.ReadOffEnd => { node _ GetPreviousNode[]; RopeReader.SetPosition[rdr, node.rope, Rope.Size[node.rope]]; RETRY }]] }; GetNextNode: PROC RETURNS [n: TextNode.RefTextNode] = { DO -- search for next text node IF (ref _ TextNode.StepForward[ref])=NIL THEN ERROR End; IF (n _ TextNode.NarrowToTextNode[ref]) # NIL THEN RETURN [n]; ENDLOOP }; GetRightChar: PROC RETURNS [CHAR] = { RETURN [RopeReader.Get[rdr ! RopeReader.ReadOffEnd => { node _ GetNextNode[]; RopeReader.SetPosition[rdr, node.rope]; RETRY }]] }; DoSelect: PROC [root: TextEdit.Ref, tSel: Selection] = { leftEnd, rightEnd: TextNode.Location; nest: CARDINAL _ 0; loc: TextNode.Offset; leftEnd _ tSel.start.pos; rightEnd _ tSel.end.pos; ref _ leftEnd.node; IF (node _ TextNode.NarrowToTextNode[ref])=NIL THEN { IF (node _ GetPreviousNode[])=NIL THEN GOTO NoMatch; loc _ Rope.Size[node.rope] } ELSE IF (loc _ leftEnd.where) = TextNode.NodeItself THEN loc _ 0; RopeReader.SetPosition[rdr, node.rope, loc]; DO -- left end SELECT GetLeftChar[ ! End => GOTO NoMatch] FROM left => IF nest=0 THEN EXIT ELSE nest _ nest-1; right => nest _ nest+1; ENDCASE; ENDLOOP; leftEnd.node _ node; leftEnd.where _ RopeReader.GetIndex[rdr]; ref _ rightEnd.node; IF (node _ TextNode.NarrowToTextNode[ref])=NIL THEN { IF (node _ GetNextNode[])=NIL THEN GOTO NoMatch; loc _ 0 } ELSE IF (loc _ rightEnd.where) = TextNode.NodeItself THEN loc _ Rope.Size[node.rope] ELSE IF pSel.granularity # point THEN loc _ loc+1; RopeReader.SetPosition[rdr, node.rope, loc]; DO -- right end SELECT GetRightChar[ ! End => GOTO NoMatch] FROM right => IF nest=0 THEN EXIT ELSE nest _ nest-1; left => nest _ nest+1; ENDCASE; ENDLOOP; rightEnd.node _ node; rightEnd.where _ RopeReader.GetIndex[rdr]-1; tSel.start.pos _ leftEnd; tSel.end.pos _ rightEnd; tSel.granularity _ char; SetSelLooks[tSel]; MakeSelection[selection: primary, new: tSel]; found _ TRUE; EXITS NoMatch => found _ FALSE }; CallWithLocks[DoSelect, read] }; NextViewer: PUBLIC PROC [forward: BOOLEAN] = BEGIN IF ~DoNextViewer[forward] THEN EditFailed["No next viewer."] END; DoNextViewer: PUBLIC PROC [forward: BOOLEAN] RETURNS [found: BOOL] = BEGIN OPEN ViewerClasses; Enumerate: PROC [enum: PROC [Viewer]] = BEGIN FOR v: Viewer _ pSel.viewer.parent.child, v.sibling UNTIL v=NIL DO enum[v]; ENDLOOP; END; thisViewer: Viewer = pSel.viewer; nextViewer: Viewer _ NIL; ConvergeForward: PROC [v: Viewer] = BEGIN IF v.class.flavor=$Text AND (v.cy > thisViewer.cy OR (v.cy = thisViewer.cy AND v.cx > thisViewer.cx)) THEN BEGIN IF nextViewer=NIL OR v.cy < nextViewer.cy THEN {nextViewer _ v; RETURN}; IF v.cy > nextViewer.cy THEN RETURN; IF (v.cy > thisViewer.cy OR v.cx > thisViewer.cx) AND v.cx < nextViewer.cx THEN nextViewer _ v; END; END; ConvergeBackward: PROC [v: Viewer] = BEGIN IF v.class.flavor=$Text AND (v.cy < thisViewer.cy OR (v.cy = thisViewer.cy AND v.cx < thisViewer.cx)) THEN BEGIN IF nextViewer=NIL OR v.cy > nextViewer.cy THEN {nextViewer _ v; RETURN}; IF v.cy < nextViewer.cy THEN RETURN; IF (v.cy < thisViewer.cy OR v.cx < thisViewer.cx) AND v.cx > nextViewer.cx THEN nextViewer _ v; END; END; LockSel[primary, "DoNextViewer"]; { ENABLE UNWIND => UnlockSel[primary]; IF pSel.viewer=NIL OR pSel.viewer.parent=NIL THEN { UnlockSel[primary]; EditFailed[]; RETURN [FALSE] }; Enumerate[IF forward THEN ConvergeForward ELSE ConvergeBackward]; IF nextViewer # NIL THEN ViewerTools.SetSelection[nextViewer, NIL]; }; UnlockSel[primary]; RETURN [nextViewer # NIL]; END; FindPlaceholders: PUBLIC PROCEDURE [next: BOOLEAN] = { found, wenttoend: BOOL; [found, wenttoend] _ DoFindPlaceholders[next, TRUE]; IF ~found AND ~wenttoend THEN NextViewer[next] }; DoFindPlaceholders: PUBLIC PROCEDURE [next, gotoend: BOOL, startBoundaryNode, endBoundaryNode: TextNode.Ref _ NIL, startBoundaryOffset: TextNode.Offset _ 0, endBoundaryOffset: TextNode.Offset _ LAST[TextNode.Offset]] RETURNS [found, wenttoend: BOOL] = { DoFind: PROC [root: TextEdit.Ref, tSel: Selection] = { finder: TreeFind.Finder _ TreeFind.CreateFromRope[IF next THEN "" ELSE ""]; from: TextNode.Location; where: TextNode.RefTextNode; at, atEnd, start: TextNode.Offset; Failed: PROC = { IF gotoend THEN { loc: TextNode.Location = IF next THEN TextNode.LastLocWithin[root] ELSE [TextNode.FirstChild[root],0]; IF loc # from OR tSel.granularity # point THEN { wenttoend _ TRUE; RememberCurrentPosition[tSel.viewer]; MakePointSelection[tSel, loc]; TEditRefresh.ScrollToEndOfSel[tSel.viewer, FALSE] }}; }; from _ InsertionPoint[tSel]; start _ from.where; IF next AND tSel.insertion=before AND tSel.granularity#point THEN start _ start+1 ELSE IF ~next AND tSel.insertion=after THEN start _ MAX[start-1,0]; [found,where,at,atEnd,,] _ IF next THEN TreeFind.Try[finder: finder, first: from.node, start: start, last: endBoundaryNode, lastLen: endBoundaryOffset] ELSE TreeFind.TryBackwards[finder: finder, first: from.node, len: start, last: startBoundaryNode, lastStart: startBoundaryOffset]; wenttoend _ FALSE; IF ~found THEN { Failed[]; RETURN }; MakePointSelection[tSel, [where,IF next THEN at+1 ELSE MAX[at,0]]]; IF ~DoSelectMatchingBrackets[','] THEN { Failed[]; RETURN }; TEditSelection.Copy[source: pSel, dest: tSel]; tSel.insertion _ before; tSel.pendingDelete _ TRUE; RememberCurrentPosition[tSel.viewer]; tSel.looks _ TextEdit.FetchLooks[ -- take looks from first char after the  TextNode.NarrowToTextNode[tSel.start.pos.node], tSel.start.pos.where+1]; MakeSelection[tSel, primary]; TEditInput.CloseEvent[]; TEditRefresh.ScrollToEndOfSel[tSel.viewer, FALSE]; }; CallWithLocks[DoFind, read] }; END.