-- TEditMiscOpsImpl.mesa Edited by Paxton on December 31, 1982 2:17 pm
Last Edited by: Maxwell, January 6, 1983 11:33 am
DIRECTORY
LongString USING [AppendChar, AppendString, AppendDecimal],
Rope USING [Fetch, 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, InsertString, 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],
Time USING [Current, Unpack, Unpacked],
TreeFind USING [Finder, CreateFromRope, Try, TryBackwards],
ViewerClasses USING [Viewer],
ViewerTools USING [SetSelection];
TEditMiscOpsImpl: CEDAR PROGRAM
IMPORTS LongString, Rope, RopeEdit, RopeReader, TextEdit, TextNode, TEditInput, TEditInputOps, TEditOps, TEditRefresh, TEditSelection, Time, 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];
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<size AND ~RopeEdit.AlphaNumericChar[TextEdit.FetchChar[node, offset]] DO
offset ← offset + 1;
nChars ← nChars + 1;
ENDLOOP;
WHILE offset<size AND RopeEdit.AlphaNumericChar[TextEdit.FetchChar[node, offset]] DO
offset ← offset + 1;
nChars ← nChars + 1;
ENDLOOP;
nChars ← nChars - 1; -- correction: loops overshoot by 1
};
DeleteNextWord: PUBLIC PROC [count: INT ← 1] = {
DoDeleteNextWord: PROC [root: TextEdit.Ref, tSel: Selection] = {
pos: TextNode.Location ← InsertionPoint[tSel];
node: TextNode.RefTextNode;
nChars, start, next: TextEdit.Offset;
IF (start ← pos.where) = TextNode.NodeItself THEN GOTO Bad;
IF (node ← TextNode.NarrowToTextNode[pos.node])=NIL THEN GOTO Bad;
next ← start;
FOR garbage:INT IN [0..count) DO next ← next+FindNextWord[node,next]; ENDLOOP;
nChars ← next-start;
Deselect[primary];
TextEdit.DeleteText[root, node, start, nChars, TEditInput.currentEvent];
MakePointSelection[tSel,pos];
TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE];
EXITS Bad => 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] };
InsertTime: PUBLIC PROC = {
dateLen: TextEdit.Offset;
TimeString: PROC RETURNS [time: REF TEXT] = TRUSTED BEGIN OPEN LongString;
s: LONG STRING; -- for convenience;
now: Time.Unpacked = Time.Unpack[Time.Current[]];
time ← NEW[TEXT[30]];
s ← LOOPHOLE[time];
AppendString[s, SELECT now.month FROM
0 => "January"L,
1 => "February"L,
2 => "March"L,
3 => "April"L,
4 => "May"L,
5 => "June"L,
6 => "July"L,
7 => "August"L,
8 => "September"L,
9 => "October"L,
10 => "November"L,
ENDCASE => "December"L];
AppendChar[s, ' ];
AppendDecimal[s, now.day];
AppendString[s , ", "];
AppendDecimal[s, now.year];
dateLen ← s.length; -- remember length of date for selection
AppendChar[s, ' ];
AppendDecimal[s, ((now.hour+11) MOD 12)+1];
AppendChar[s, ':];
AppendDecimal[s, now.minute/10];
AppendDecimal[s, now.minute MOD 10];
AppendString[s, IF now.hour<12 THEN " am" ELSE " pm"];
END;
DoInsertTime: PROC [root: TextEdit.Ref, tSel: Selection] = {
resLen: TextEdit.Offset;
caret: TextNode.Location;
dest: TextNode.RefTextNode;
looks: TextLooks.Looks;
looks ← pSel.looks;
IF pSel.pendingDelete THEN DoPendingDelete[];
caret ← InsertionPoint[pSel];
IF (dest ← TextNode.NarrowToTextNode[caret.node])=NIL THEN GOTO Bad;
Deselect[primary];
[----, resLen] ← TextEdit.InsertString[root: root, dest: dest, string: TimeString[],
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.