TEditMiscOpsImpl.mesa
Paxton on June 6, 1983 4:09 pm
Maxwell, January 6, 1983 11:33 am
Russ Atkinson, September 28, 1983 2:57 pm
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<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] };
AppendDecimal:
PROC [text:
REF
TEXT, number:
NAT] = {
RRA wrote this
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.