TEditMiscOpsImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Paxton on June 6, 1983 4:09 pm
Maxwell, January 6, 1983 11:33 am
Russ Atkinson, September 28, 1983 2:57 pm
Michael Plass, April 7, 1986 9:09:08 am PST
Doug Wyatt, April 8, 1985 5:28:45 pm PST
Russ Atkinson (RRA) January 23, 1986 0:10:22 am PST
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 = {
copy leading blanks from previous line
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<size AND NOT Alpha[offset] DO
offset ← offset + 1;
nChars ← nChars + 1;
ENDLOOP;
WHILE offset<size AND Alpha[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: TextNode.Ref, tSel: Selection] = {
pos: TextNode.Location ← InsertionPoint[tSel];
node: TextNode.RefTextNode ← pos.node;
nChars, start, next: TextEdit.Offset;
IF (start ← pos.where) = TextNode.NodeItself THEN GOTO Bad;
IF 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: 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] = {
RRA wrote this
pos: NAT ← text.length;
skipZeros: BOOLTRUE;
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.ROPEIO.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: BOOLFALSE] = {
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.