TEditMiscOpsImpl.mesa
Copyright © 1985, 1986 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
Russ Atkinson (RRA) January 23, 1986 0:10:22 am PST
Doug Wyatt, September 2, 1986 4:49:33 pm PDT
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, Size],
TextLooks USING [Look, Looks, noLooks],
TextNode USING [Backward, FirstChild, LastLocWithin, Location, Node, NodeItself, Parent, StepForward, StepBackward],
TEditDocument USING [BeforeAfter, Selection, SelectionPoint],
TEditInput USING [CloseEvent, currentEvent],
TEditInputOps USING [CallWithLocks, DoPendingDelete, EditFailed],
TEditOps USING [RememberCurrentPosition],
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.Node, 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.Node, tSel: Selection] = {
pos: TextNode.Location;
len: INT ← 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.Node, tSel: Selection] = {
pos: TextNode.Location;
node: TextNode.Node;
start, end, lim: INT ← 0;
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.Node, tSel: Selection] = {
flush: TextNode.Location ← InsertionPoint[tSel];
node: TextNode.Node ← 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.Node, tSel: Selection] = {
loc: TextNode.Location ← InsertionPoint[tSel];
node: TextNode.Node ← 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.Node, tSel: Selection] = {
loc: TextNode.Location ← InsertionPoint[tSel];
node: TextNode.Node ← 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.Node, start:
INT]
RETURNS [nChars: CARDINAL] = {
offset: INT ← start;
size: INT ← 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.Node, tSel: Selection] = {
pos: TextNode.Location ← InsertionPoint[tSel];
node: TextNode.Node ← pos.node;
nChars, start, next: INT ← 0;
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.Node, tSel: Selection] = {
pos: TextNode.Location ← InsertionPoint[tSel];
node: TextNode.Node ← pos.node;
size, next: INT ← 0;
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.Node, tSel: Selection] = {
pos: TextNode.Location ← InsertionPoint[tSel];
node: TextNode.Node ← 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.Node, tSel: Selection] = {
pos: TextNode.Location ← InsertionPoint[tSel];
node: TextNode.Node ← 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: 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.Node, tSel: Selection] = {
resLen: INT ← 0;
caret: TextNode.Location;
dest: TextNode.Node;
looks: TextLooks.Looks ← pSel.looks;
date: Rope.ROPE ← IO.PutR[[time[BasicTime.Now[]]]];
dateLen: INT ← 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.Node, tSel: Selection] = {
leftEnd, rightEnd: TextNode.Location;
leftNode, rightNode: TextNode.Node;
l, r: INT ← 0;
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.Node;
node: TextNode.Node;
GetPreviousNode:
PROC
RETURNS [n: TextNode.Node] = {
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.Node] = {
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.Node, tSel: Selection] = {
leftEnd, rightEnd: TextNode.Location;
nest: CARDINAL ← 0;
loc: INT ← 0;
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.
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.Node ← NIL,
startBoundaryOffset: INT ← 0,
endBoundaryOffset: INT ← LAST[INT]]
RETURNS [found, wenttoend: BOOL] = {
DoFind:
PROC [root: TextNode.Node, tSel: Selection] = {
finder: TreeFind.Finder ← TreeFind.CreateFromRope[IF next THEN "" ELSE ""];
from: TextNode.Location;
where: TextNode.Node;
at, atEnd, start: INT ← 0;
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.