<> <> <> <> <> <> <> <> <<>> 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, RefTextNode, Size], TextLooks USING [Look, Looks, noLooks], TextNode USING [Backward, FirstChild, LastLocWithin, Location, NodeItself, Offset, Parent, Ref, RefTextNode, StepForward, StepBackward], 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: CHAR] = { DoInsertChar: PROC [root: TextNode.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: 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: TextNode.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: 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 = { <> DoInsertLineBreak: PROC [root: TextNode.Ref, tSel: Selection] = { pos: TextNode.Location; node: TextNode.RefTextNode; start, end, lim: 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 _ pos.node) = NIL THEN { EditFailed[]; RETURN }; rope _ node.rope; start _ lim _ MAX[0, MIN[pos.where, Rope.Size[rope]]]; WHILE start > 0 AND Rope.Fetch[rope,start-1] # 15C DO start _ start-1; ENDLOOP; end _ start; WHILE end < lim 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: TextNode.Ref, tSel: Selection] = { flush: TextNode.Location _ InsertionPoint[tSel]; node: TextNode.RefTextNode _ flush.node; IF 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: TextNode.Ref, tSel: Selection] = { loc: TextNode.Location _ InsertionPoint[tSel]; node: TextNode.RefTextNode _ loc.node; IF 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: TextNode.Ref, tSel: Selection] = { loc: TextNode.Location _ InsertionPoint[tSel]; node: TextNode.RefTextNode _ loc.node; IF 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]; Alpha: PROC [index: INT] RETURNS [BOOL] ~ { set: NAT; char: CHAR; [set, char] _ TextEdit.FetchChar[node, index]; RETURN [set=0 AND RopeEdit.AlphaNumericChar[char]]; }; nChars _ 1; WHILE offset EditFailed[]; }; IF count > 0 THEN CallWithLocks[DoDeleteNextWord]; }; GoToNextWord: PUBLIC PROC [count: INT _ 1] = { DoGoToNextWord: PROC [root: TextNode.Ref, tSel: Selection] = { pos: TextNode.Location _ InsertionPoint[tSel]; node: TextNode.RefTextNode _ pos.node; size, next: TextEdit.Offset; IF 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: TextNode.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: TextNode.Ref, tSel: Selection] = { 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 node=NIL THEN GOTO Bad; MakePointSelection[tSel,[node,TextEdit.Size[node]]]; 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: TextNode.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 _ 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]; }; MakeLook: PROC [c: CHAR] RETURNS [looks: TextLooks.Looks] ~ INLINE { looks _ ALL[FALSE]; looks[c] _ TRUE; }; InsertBrackets: PUBLIC PROC [left, right: CHAR] = { <<-- insert left char at start of selection, right char after selection>> DoInsertBrackets: PROC [root: TextNode.Ref, tSel: Selection] = { leftEnd, rightEnd: TextNode.Location; leftNode, rightNode: TextNode.RefTextNode; l, r: TextNode.Offset; bracketLooks: TextLooks.Looks ~ IF left = 1C THEN MakeLook['t] ELSE tSel.looks; leftEnd _ tSel.start.pos; rightEnd _ tSel.end.pos; IF (leftNode _ leftEnd.node)=NIL THEN GOTO Bad; IF (rightNode _ 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: bracketLooks, event: TEditInput.currentEvent]; [----, ----] _ TextEdit.InsertChar[ root: root, dest: leftNode, char: left, destLoc: l, inherit: FALSE, looks: bracketLooks, 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 NOT 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 [back: ref, backparent: parent] _ TextNode.Backward[ref, parent]; IF ref=NIL THEN ERROR End; IF ref # NIL THEN RETURN [ref]; ENDLOOP }; GetLeftChar: PROC RETURNS [CHAR] = { c: CHAR; c _ RopeReader.Backwards[rdr ! RopeReader.ReadOffEnd => { node _ GetPreviousNode[]; RopeReader.SetPosition[rdr, node.rope, Rope.Size[node.rope]]; RETRY } ]; RETURN [c]; }; GetNextNode: PROC RETURNS [n: TextNode.RefTextNode] = { DO -- search for next text node IF (ref _ TextNode.StepForward[ref])=NIL THEN ERROR End; IF ref # NIL THEN RETURN [ref]; ENDLOOP; }; GetRightChar: PROC RETURNS [CHAR] = { c: CHAR; c _ RopeReader.Get[rdr ! RopeReader.ReadOffEnd => { node _ GetNextNode[]; RopeReader.SetPosition[rdr, node.rope]; RETRY } ]; RETURN [c] }; DoSelect: PROC [root: TextNode.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 _ 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 _ 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]; }; CallWithPrimarySelectionLocked: PROC [who: ROPE, action: PROC [Selection]] ~ { TEditSelection.LockSel[primary, who]; action[TEditSelection.pSel ! UNWIND => TEditSelection.UnlockSel[primary]]; TEditSelection.UnlockSel[primary]; }; NextViewer: PUBLIC PROC [forward: BOOL] = { IF NOT DoNextViewer[forward] THEN EditFailed["No next viewer."]; }; DoNextViewer: PUBLIC PROC [forward: BOOL] RETURNS [found: BOOL _ FALSE] = { Viewer: TYPE ~ ViewerClasses.Viewer; DoNextViewerAction: PROC [pSel: Selection] ~ { thisViewer: Viewer ~ pSel.viewer; nextViewer: Viewer _ NIL; Enumerate: PROC [enum: PROC [Viewer]] = { FOR v: Viewer _ thisViewer.parent.child, v.sibling UNTIL v=NIL DO enum[v]; ENDLOOP; }; ConvergeForward: PROC [v: Viewer] = { IF v.class.flavor=$Text AND (v.wy > thisViewer.wy OR (v.wy = thisViewer.wy AND v.wx > thisViewer.wx)) THEN { IF nextViewer=NIL OR v.wy < nextViewer.wy THEN {nextViewer _ v; RETURN}; IF v.wy > nextViewer.wy THEN RETURN; IF (v.wy > thisViewer.wy OR v.wx > thisViewer.wx) AND v.wx < nextViewer.wx THEN nextViewer _ v; }; }; ConvergeBackward: PROC [v: Viewer] = { IF v.class.flavor=$Text AND (v.wy < thisViewer.wy OR (v.wy = thisViewer.wy AND v.wx < thisViewer.wx)) THEN { IF nextViewer=NIL OR v.wy > nextViewer.wy THEN {nextViewer _ v; RETURN}; IF v.wy < nextViewer.wy THEN RETURN; IF (v.wy < thisViewer.wy OR v.wx < thisViewer.wx) AND v.wx > nextViewer.wx THEN nextViewer _ v; }; }; IF thisViewer=NIL OR thisViewer.parent=NIL THEN RETURN; Enumerate[IF forward THEN ConvergeForward ELSE ConvergeBackward]; IF nextViewer # NIL THEN { ViewerTools.SetSelection[nextViewer, NIL]; found _ TRUE }; }; CallWithPrimarySelectionLocked["DoNextViewer", DoNextViewerAction]; }; FindPlaceholders: PUBLIC PROC [next: BOOL] = { found, wenttoend: BOOL; [found, wenttoend] _ DoFindPlaceholders[next, TRUE]; IF ~found AND ~wenttoend THEN NextViewer[next]; }; DoFindPlaceholders: PUBLIC PROC [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: TextNode.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 NOT 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 NOT found THEN { Failed[]; RETURN }; MakePointSelection[tSel, [where,IF next THEN at+1 ELSE MAX[at,0]]]; IF NOT 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  tSel.start.pos.node, tSel.start.pos.where+1]; MakeSelection[tSel, primary]; TEditInput.CloseEvent[]; TEditRefresh.ScrollToEndOfSel[tSel.viewer, FALSE]; }; CallWithLocks[DoFind, read]; }; END.