TEditMiscOpsImpl.mesa
Copyright Ó 1985, 1987, 1988 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, October 19, 1987 11:08:22 am PDT
Russ Atkinson (RRA) January 23, 1986 0: 10: 22 am PST
Doug Wyatt, February 17, 1988 6:21:49 pm PST
DIRECTORY
AbbrevExpand USING [Expand, Load],
Ascii USING [Lower],
Atom USING [GetPName, MakeAtom, PropList],
Basics USING [BITAND],
BasicTime USING [Now],
EditSpan USING [ChangeCaps],
FS USING [Error],
IO USING [PutR],
MessageWindow USING [Append, Blink],
NodeProps USING [false, true],
NodeStyleOps USING [ReloadStyle, StyleNameForNode],
NodeStyleWorks USING [ForEachAttachedStyle],
Rope USING [Concat, Fetch, FromProc, Index, IsEmpty, ROPE, Size, Substr],
RopeEdit USING [AlphaNumericChar, BlankChar, Concat, Substr],
RopeReader USING [Backwards, Create, FreeRopeReader, Get, GetIndex, GetRope, GetRopeReader, ReadOffEnd, Ref, SetPosition],
TEditDocument USING [Selection, SelectionId],
TEditInput USING [CloseEvent, currentEvent, Interpret],
TEditInputOps USING [BackSpace, CallWithBothLocked, CallWithLocks, CheckReadonly, Delete, DoPendingDelete, EditFailed, FindPlaceholders, InsertChar, InsertRope],
TEditLocks USING [Lock, Unlock],
TEditOps USING [RememberCurrentPosition],
TEditRefresh USING [ScrollToEndOfSel],
TEditScrolling USING [AutoScroll],
TEditSelection USING [Alloc, CaretAfterSelection, CaretVisible, Copy, Deselect, Free, InsertionPoint, LockSel, MakePointSelection, MakeSelection, oldSel, pSel, SelectionRoot, SetSelLooks, sSel, UnlockSel],
TEditTouchup USING [fullUpdate],
TextEdit USING [CapChange, ChangeFormat, ChangeStyle, DeleteText, FetchChar, FetchCharPropList, FetchLooks, InsertChar, InsertRope, PutCharPropList, PutProp, ReplaceByChar, Size, XChar],
TextLooks USING [Looks],
TextNode USING [Backward, FirstChild, LastLocWithin, Location, Node, NodeItself, Parent, Root, StepBackward, StepForward],
TreeFind USING [CreateFromRope, Finder, Try, TryBackwards],
UndoEvent USING [],
ViewerClasses USING [Viewer],
ViewerOps USING [PaintViewer],
ViewerTools USING [SetSelection];
TEditMiscOpsImpl: CEDAR PROGRAM
IMPORTS AbbrevExpand, Ascii, Atom, Basics, BasicTime, EditSpan, FS, IO, MessageWindow, NodeProps, NodeStyleOps, NodeStyleWorks, Rope, RopeEdit, RopeReader, TEditInput, TEditInputOps, TEditLocks, TEditOps, TEditRefresh, TEditScrolling, TEditSelection, TEditTouchup, TextEdit, TextNode, TreeFind, ViewerOps, ViewerTools
EXPORTS TEditInputOps, TEditOps
= BEGIN
Node: TYPE ~ TextNode.Node;
ROPE: TYPE ~ Rope.ROPE;
XChar: TYPE ~ TextEdit.XChar;
Capitalise: PUBLIC PROC [flavor: TextEdit.CapChange] = {
DoCapitalise: PROC [root: Node, tSel: TEditDocument.Selection] = {
TEditSelection.Deselect[];
EditSpan.ChangeCaps[root, [tSel.start.pos, tSel.end.pos], flavor, TEditInput.currentEvent];
tSel.pendingDelete ← FALSE;
TEditSelection.MakeSelection[tSel, primary]
};
TEditInputOps.CallWithLocks[DoCapitalise]
};
ControlChar: PROC [make: BOOLEAN] = {
DoControlChar: PROC [root: Node, tSel: TEditDocument.Selection] = {
caret: TextNode.Location ← TEditSelection.InsertionPoint[tSel];
node: Node;
where: INT;
char: XChar;
IF caret.where=TextNode.NodeItself OR caret.where=0 THEN GOTO Bad;
IF (node ← caret.node) = NIL THEN GOTO Bad;
where ← caret.where-1;
char ← TextEdit.FetchChar[node, where];
IF char.set#0 THEN GOTO Bad;
IF make
THEN char.code ← Basics.BITAND[char.code, 37B]
ELSE IF char.code<40B THEN char.code ← ORD['@]+char.code ELSE GOTO Bad;
TEditSelection.Deselect[];
[] ← TextEdit.ReplaceByChar[root: root, dest: node, char: VAL[char.code], start: where, len: 1, inherit: FALSE, looks: TextEdit.FetchLooks[node, where], charSet: char.set, event: TEditInput.currentEvent];
TEditSelection.MakePointSelection[tSel, caret];
EXITS Bad => TEditInputOps.EditFailed[]
};
TEditInputOps.CallWithLocks[DoControlChar]
};
MakeControlCharacter: PUBLIC PROC = { ControlChar[TRUE] };
UnMakeControlCharacter: PUBLIC PROC = { ControlChar[FALSE] };
UnMakeOctalCharacter: PUBLIC PROC = {
DoUnMakeOctalCharacter: PROC [root: Node, tSel: TEditDocument.Selection] = {
InsertOctal: PROC [c: CARDINAL] = {
TEditInputOps.InsertChar[c/64 + '0];
TEditInputOps.InsertChar[(c/8) MOD 8 + '0];
TEditInputOps.InsertChar[c MOD 8 + '0];
};
caret: TextNode.Location ~ TEditSelection.InsertionPoint[tSel];
node: Node ~ caret.node;
char: XChar;
charProps: Atom.PropList;
IF caret.where=TextNode.NodeItself OR caret.where=0 THEN GOTO Bad;
IF node = NIL THEN GOTO Bad;
char ← TextEdit.FetchChar[node, caret.where-1];
charProps ← TextEdit.FetchCharPropList[node, caret.where-1];
TEditInputOps.BackSpace[1];
IF char.set = 0 THEN InsertOctal[char.code]
ELSE {
TEditInputOps.InsertChar['(];
InsertOctal[char.set];
TEditInputOps.InsertChar['|];
InsertOctal[char.code];
TEditInputOps.InsertChar[')];
};
IF charProps # NIL THEN {
TextEdit.PutCharPropList[node: node, index: caret.where-1, nChars: IF char.set = 0 THEN 3 ELSE 9, propList: charProps, event: TEditInput.currentEvent]
};
EXITS Bad => TEditInputOps.EditFailed[]
};
TEditInputOps.CallWithLocks[DoUnMakeOctalCharacter]
};
MakeOctalCharacter: PUBLIC PROC = {
DoMakeOctalCharacter: PROC [root: Node, tSel: TEditDocument.Selection] = {
caret: TextNode.Location ← TEditSelection.InsertionPoint[tSel];
node: Node ~ caret.node;
GetOctal: PROC [offset: INT] RETURNS [d: CARDINAL ← 0] = {
char: XChar;
FOR i: INT IN [0..3) DO
char ← TextEdit.FetchChar[node, offset+i];
IF char.set#0 OR char.code NOT IN [ORD['0]..ORD['7]] THEN RETURN [NAT.LAST];
d ← d*8 + (char.code-ORD['0]);
ENDLOOP;
};
len: INT ← 0;
char: XChar ← [0, 0];
IF caret.where=TextNode.NodeItself OR caret.where<3 THEN GOTO Bad;
IF node = NIL THEN GOTO Bad;
IF TextEdit.FetchChar[node, caret.where-1] = [0, ORD[')]] THEN {
IF caret.where<9 THEN GOTO Bad;
IF TextEdit.FetchChar[node, caret.where-5] # [0, ORD['|]] THEN GOTO Bad;
IF TextEdit.FetchChar[node, caret.where-9] # [0, ORD['(]] THEN GOTO Bad;
char.code ← GetOctal[caret.where-4];
char.set ← GetOctal[caret.where-8];
len ← 9;
}
ELSE {
char.code ← GetOctal[caret.where-3];
len ← 3;
};
IF char.code > 255 OR char.set >= 255 THEN GOTO Bad;
TEditSelection.Deselect[]; {
charProps: Atom.PropList ~ TextEdit.FetchCharPropList[node, caret.where-len];
[] ← TextEdit.ReplaceByChar[
root: root, dest: node, char: VAL[char.code], start: caret.where-len, len: len,
inherit: FALSE, looks: TextEdit.FetchLooks[node, caret.where-len], charSet: char.set, event: TEditInput.currentEvent];
IF charProps # NIL THEN TextEdit.PutCharPropList[node: node, index: caret.where-len, nChars: 1, propList: charProps, event: TEditInput.currentEvent]
};
caret.where ← caret.where-len+1;
TEditSelection.MakePointSelection[tSel, caret];
EXITS Bad => TEditInputOps.EditFailed[]
};
TEditInputOps.CallWithLocks[DoMakeOctalCharacter]
};
GetWord: PROC RETURNS [word: ROPE] = {
tSel: TEditDocument.Selection;
start: INT;
pos: TextNode.Location;
node: Node;
nChars: CARDINAL ← 0;
TEditSelection.CaretAfterSelection;
pos ← TEditSelection.InsertionPoint[];
IF (node ← pos.node)=NIL THEN RETURN [NIL];
IF pos.where = TextNode.NodeItself THEN RETURN [NIL];
start ← pos.where-1;
WHILE start>=0 DO
char: XChar ~ TextEdit.FetchChar[node, start];
IF char.set#0 OR NOT RopeEdit.AlphaNumericChar[VAL[char.code]] THEN EXIT;
start ← start - 1; nChars ← nChars + 1;
ENDLOOP;
IF nChars = 0 THEN RETURN [NIL];
start ← pos.where-nChars;
word ← RopeEdit.Substr[node.rope, start, nChars];
tSel ← TEditSelection.Alloc[];
TEditSelection.Copy[source: TEditSelection.pSel, dest: tSel];
tSel.start.pos ← [node, start];
tSel.end.pos ← [node, start+nChars-1];
tSel.granularity ← char;
TEditSelection.MakeSelection[tSel, primary];
TEditSelection.Free[tSel];
TEditInputOps.Delete[];
};
SetStyle: PUBLIC PROC = {
DoSetStyle: PROC [root: Node, tSel: TEditDocument.Selection] = {
name: ROPE ← GetWord[];
IF name=NIL THEN { TEditInputOps.EditFailed[]; RETURN };
SetStyleName[name]
};
TEditInputOps.CallWithLocks[DoSetStyle]
};
ForceLower: PROC [r: ROPE] RETURNS [ROPE] = {
Force: PROC RETURNS [c: CHAR] = { c ← Ascii.Lower[Rope.Fetch[r, i]]; i ← i+1 };
i: INT ← 0;
RETURN [Rope.FromProc[Rope.Size[r], Force]]
};
SetStyleName: PUBLIC PROC [name: ROPE, node: Node ← NIL] = {
root: Node;
IF node=NIL THEN
node ← IF TEditSelection.pSel.insertion=before THEN TEditSelection.pSel.start.pos.node ELSE TEditSelection.pSel.end.pos.node;
root ← TextNode.Root[node];
[] ← TEditLocks.Lock[root, "SetStyleName"];
TextEdit.ChangeStyle[node, ForceLower[name], TEditInput.currentEvent, root !
UNWIND => TEditLocks.Unlock[root]];
TEditLocks.Unlock[root];
};
ReloadStyle: PUBLIC PROC = {
styleName: ATOM ← NodeStyleOps.StyleNameForNode[TEditSelection.InsertionPoint[].node];
IF NOT NodeStyleOps.ReloadStyle[styleName] THEN CannotReload[styleName]
ELSE ViewerOps.PaintViewer[TEditSelection.pSel.viewer, client, FALSE, TEditTouchup.fullUpdate];
};
ReloadStyleName: PUBLIC PROC [name: ROPE] = {
styleName: ATOM;
IF name.IsEmpty THEN RETURN;
styleName ← Atom.MakeAtom[name];
IF NOT NodeStyleOps.ReloadStyle[styleName] THEN CannotReload[styleName];
};
CannotReload: PROC [styleName: ATOM] = {
MessageWindow.Append["Failed in attempt to load style named ", TRUE];
MessageWindow.Append[Atom.GetPName[styleName]];
MessageWindow.Blink[]
};
SetFormat: PUBLIC PROC = {
DoSetFormat: PROC [root: Node, tSel: TEditDocument.Selection] = {
name: ROPE ← GetWord[];
IF name=NIL THEN { TEditInputOps.EditFailed[]; RETURN };
SetFormatName[name]
};
TEditInputOps.CallWithLocks[DoSetFormat]
};
GetFormat: PUBLIC PROC = {
DoGetFormat: PROC [root: Node, tSel: TEditDocument.Selection] = {
name: ROPE;
format: ATOM ← TEditSelection.InsertionPoint[].node.formatName;
name ← IF format=NIL THEN "default" ELSE Atom.GetPName[format];
TEditInputOps.InsertRope[name]
};
TEditInputOps.CallWithLocks[DoGetFormat]
};
TransposeFormat: PUBLIC PROC [target: TEditDocument.SelectionId ← primary] = {
Transpose the formats of the primary and secondary selections
targetSel: TEditDocument.Selection ← IF target=primary THEN TEditSelection.pSel ELSE TEditSelection.sSel;
srcSel: TEditDocument.Selection ← IF target=primary THEN TEditSelection.sSel ELSE TEditSelection.pSel;
DoTransFormat: PROC [sourceRoot, destRoot: Node, tSel, srcSel, targetSel: TEditDocument.Selection] = {
targetFormat, srcFormat: ATOM;
targetNode, srcNode: Node;
srcNode ← IF srcSel.insertion=before THEN srcSel.start.pos.node ELSE srcSel.end.pos.node;
srcFormat ← srcNode.formatName;
targetNode ← IF targetSel.insertion=before THEN targetSel.start.pos.node
ELSE targetSel.end.pos.node;
targetFormat ← targetNode.formatName;
TEditSelection.Copy[source: targetSel, dest: TEditSelection.oldSel]; -- save for Repeat's
FOR node: Node ← targetSel.start.pos.node, TextNode.StepForward[node] DO
TextEdit.ChangeFormat[node, srcFormat, TEditInput.currentEvent, destRoot];
IF node = targetSel.end.pos.node THEN EXIT;
ENDLOOP;
FOR node: Node ← srcSel.start.pos.node, TextNode.StepForward[node] DO
TextEdit.ChangeFormat[node, targetFormat, TEditInput.currentEvent, sourceRoot];
IF node = srcSel.end.pos.node THEN EXIT;
ENDLOOP;
TEditSelection.MakeSelection[IF target=primary THEN targetSel ELSE srcSel, primary]
};
TEditInputOps.CallWithBothLocked[DoTransFormat, targetSel, srcSel, write]
};
CopyFormat: PUBLIC PROC [target: TEditDocument.SelectionId ← primary] = {
targetSel: TEditDocument.Selection ← IF target=primary THEN TEditSelection.pSel ELSE TEditSelection.sSel;
srcSel: TEditDocument.Selection ← IF target=primary THEN TEditSelection.sSel ELSE TEditSelection.pSel;
DoCopyFormat: PROC [sourceRoot, destRoot: Node, tSel, srcSel, targetSel: TEditDocument.Selection] = {
srcNode: Node ←
IF srcSel.insertion=before THEN srcSel.start.pos.node ELSE srcSel.end.pos.node;
format: ATOM ← srcNode.formatName;
TEditSelection.Copy[source: srcSel, dest: TEditSelection.oldSel]; -- save for Repeat's
FOR node: Node ← targetSel.start.pos.node, TextNode.StepForward[node] DO
TextEdit.ChangeFormat[node, format, TEditInput.currentEvent, destRoot];
IF node = targetSel.end.pos.node THEN EXIT;
ENDLOOP;
TEditSelection.MakeSelection[IF target=primary THEN targetSel ELSE srcSel, primary]
};
TEditInputOps.CallWithBothLocked[DoCopyFormat, targetSel, srcSel, read]
};
SetFormatName: PUBLIC PROC [name: ROPE, node: Node ← NIL] = {
root: Node;
lockSel: BOOL = (node=NIL);
format: ATOMIF name.IsEmpty THEN NIL ELSE Atom.MakeAtom[ForceLower[name]];
IF lockSel THEN {
TEditSelection.LockSel[primary, "SetFormatName"];
IF NOT TEditInputOps.CheckReadonly[TEditSelection.pSel] OR (root ← TEditSelection.SelectionRoot[TEditSelection.pSel])=NIL THEN {
TEditSelection.UnlockSel[primary]; RETURN
};
node ← TEditSelection.InsertionPoint[].node
}
ELSE root ← TextNode.Root[node];
{ ENABLE UNWIND => IF lockSel THEN TEditSelection.UnlockSel[primary];
[] ← TEditLocks.Lock[root, "SetFormatName"];
TextEdit.ChangeFormat[node, format, TEditInput.currentEvent, root];
TEditLocks.Unlock[root];
IF lockSel THEN TEditSelection.UnlockSel[primary]
}
};
SetSelFormatName: PROC [name: ROPE, sel: TEditDocument.Selection] = {
DoSetSelFormatName: PROC [root: Node, tSel: TEditDocument.Selection] = {
format: ATOMIF name.IsEmpty THEN NIL ELSE Atom.MakeAtom[ForceLower[name]];
FOR node: Node ← tSel.start.pos.node, TextNode.StepForward[node] DO
TextEdit.ChangeFormat[node, format, TEditInput.currentEvent, root];
IF node = tSel.end.pos.node THEN EXIT;
ENDLOOP
};
TEditInputOps.CallWithLocks[DoSetSelFormatName]
};
SetCommentProp: PUBLIC PROC [flag: BOOLEAN] = {
DoSetCommentProp: PROC [root: Node, tSel: TEditDocument.Selection] = {
FOR node: Node ← tSel.start.pos.node, TextNode.StepForward[node] DO
n: Node ← node;
IF n # NIL THEN
TextEdit.PutProp[n, $Comment,
IF flag THEN NodeProps.true ELSE NodeProps.false,
TEditInput.currentEvent];
IF node = tSel.end.pos.node THEN EXIT;
ENDLOOP
};
TEditInputOps.CallWithLocks[DoSetCommentProp]
};
abbrevFailedProc: PROC RETURNS [BOOL] ← NIL;
RegisterAbbrevFailedProc: PUBLIC PROC [proc: PROC RETURNS [BOOL]] = {
abbrevFailedProc ← proc
};
ExpandAbbreviation: PUBLIC PROC = {
DoExpand: PROC [root: Node, tSel: TEditDocument.Selection] = {
pos: TextNode.Location;
node: Node;
offset: INT;
done, keyDeterminesDict: BOOLEANFALSE;
clearedMessageWindow: BOOLEANFALSE;
keyStart, keyLen, resultLen: INT;
rdr: RopeReader.Ref;
commands: LIST OF REF ANY;
styleName: ATOM;
Try: PROC [name: ATOM] RETURNS [stop: BOOLEAN] = {
dict: ROPE = Atom.GetPName[name];
[done, keyDeterminesDict, keyStart, keyLen, resultLen, commands] ←
AbbrevExpand.Expand[node, offset, dict, TEditInput.currentEvent];
IF NOT done AND NOT keyDeterminesDict THEN
NodeStyleWorks.ForEachAttachedStyle[name, Try];
RETURN [done]
};
TEditSelection.CaretAfterSelection;
pos ← TEditSelection.InsertionPoint[];
node ← pos.node;
offset ← pos.where;
styleName ← NodeStyleOps.StyleNameForNode[node];
TEditSelection.Deselect[];
IF NOT Try[styleName] THEN {
TEditSelection.MakeSelection[tSel, primary];
IF abbrevFailedProc # NIL AND abbrevFailedProc[] THEN RETURN;
MessageWindow.Append[Rope.Substr[node.rope, keyStart, keyLen], NOT clearedMessageWindow];
MessageWindow.Append["? Unknown abbreviation."];
RETURN
};
tSel.end.pos.node ← tSel.start.pos.node ← node;
tSel.start.pos.where ← keyStart;
tSel.pendingDelete ← FALSE;
IF resultLen = 0 THEN { -- make a caret
tSel.end.pos.where ← keyStart;
tSel.insertion ← before;
tSel.granularity ← point
}
ELSE {
tSel.end.pos.where ← keyStart+resultLen-1;
tSel.insertion ← after;
tSel.granularity ← char
};
TEditSelection.MakeSelection[tSel, primary];
rdr ← RopeReader.GetRopeReader[];
RopeReader.SetPosition[rdr, node.rope, keyStart];
FOR i: INT IN [0..resultLen) DO -- check for placeholder
IF RopeReader.Get[rdr] = 1C THEN { -- found one
TEditSelection.MakePointSelection[tSel, [node, keyStart+i]];
TEditInputOps.FindPlaceholders[TRUE];
EXIT
};
ENDLOOP;
RopeReader.FreeRopeReader[rdr];
IF commands # NIL THEN TEditInput.Interpret[TEditSelection.pSel.viewer, commands]
};
TEditInputOps.CallWithLocks[DoExpand]
};
LoadAbbreviations: PUBLIC PROC [dictName: ROPE] = {
count: LONG INTEGER ← 0;
fileName: ROPE;
IF Rope.Size[dictName]=0 THEN RETURN;
fileName ← Rope.Concat[dictName, ".Abbreviations"];
count ← AbbrevExpand.Load[fileName,
dictName ! FS.Error => {CONTINUE}];
IF count = 0 THEN { -- something went wrong
MessageWindow.Append["The file named <", TRUE];
MessageWindow.Append[fileName];
MessageWindow.Append["> was not found or was not an abbreviation dictionary"]
}
};
InsertChar: PUBLIC PROC [char: CHAR] = {
DoInsertChar: PROC [root: Node, tSel: TEditDocument.Selection] = {
pos: TextNode.Location;
looks: TextLooks.Looks ← tSel.looks;
IF tSel.pendingDelete THEN {
TEditInputOps.DoPendingDelete[];
TEditSelection.Copy[source: TEditSelection.pSel, dest: tSel]
};
pos ← TEditSelection.InsertionPoint[TEditSelection.pSel]; -- need to get insertion point after pending delete
TEditSelection.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;
TEditSelection.MakePointSelection[tSel, pos];
IF TEditSelection.CaretVisible[] THEN TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE]
};
TEditInputOps.CallWithLocks[DoInsertChar];
};
InsertRope: PUBLIC PROC [rope: ROPE] = {
DoInsertRope: PROC [root: Node, tSel: TEditDocument.Selection] = {
pos: TextNode.Location;
len: INT ← Rope.Size[rope];
looks: TextLooks.Looks;
IF tSel.pendingDelete THEN {
TEditInputOps.DoPendingDelete[];
TEditSelection.Copy[source: TEditSelection.pSel, dest: tSel]
};
pos ← TEditSelection.InsertionPoint[TEditSelection.pSel]; -- need to get insertion point after pending delete
looks ← tSel.looks;
TEditSelection.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;
TEditSelection.MakePointSelection[tSel, pos];
IF TEditSelection.CaretVisible[] THEN TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE]
};
TEditInputOps.CallWithLocks[DoInsertRope];
};
InsertLineBreak: PUBLIC PROC = {
copy leading blanks from previous line
DoInsertLineBreak: PROC [root: Node, tSel: TEditDocument.Selection] = {
pos: TextNode.Location;
node: Node;
start, end, lim: INT;
rope: ROPE;
IF tSel.pendingDelete THEN {
TEditInputOps.DoPendingDelete[];
TEditSelection.Copy[source: TEditSelection.pSel, dest: tSel]
};
pos ← TEditSelection.InsertionPoint[TEditSelection.pSel]; -- need to get insertion point after pending delete
IF (node ← pos.node) = NIL THEN { TEditInputOps.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;
TEditInputOps.InsertRope[RopeEdit.Concat["\n", RopeEdit.Substr[rope, start, end-start]]];
};
TEditInputOps.CallWithLocks[DoInsertLineBreak];
};
DeleteNextChar: PUBLIC PROC [count: INT ← 1] = {
DoDeleteNextChar: PROC [root: Node, tSel: TEditDocument.Selection] = {
flush: TextNode.Location ← TEditSelection.InsertionPoint[tSel];
node: 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;
TEditSelection.Deselect[primary];
TextEdit.DeleteText[root, node, flush.where, count, TEditInput.currentEvent];
TEditSelection.MakePointSelection[tSel, flush];
TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE];
EXITS Bad => TEditInputOps.EditFailed[]
};
IF count > 0 THEN TEditInputOps.CallWithLocks[DoDeleteNextChar];
};
GoToNextChar: PUBLIC PROC [count: INT ← 1] = {
DoGoToNextChar: PROC [root: Node, tSel: TEditDocument.Selection] = {
loc: TextNode.Location ← TEditSelection.InsertionPoint[tSel];
node: 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
};
TEditSelection.MakePointSelection[tSel, [node, loc.where+count]];
TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE];
};
IF count > 0 THEN TEditInputOps.CallWithLocks[DoGoToNextChar, read];
};
GoToPreviousChar: PUBLIC PROC [count: INT ← 1] = {
DoGoToPreviousChar: PROC [root: Node, tSel: TEditDocument.Selection] = {
loc: TextNode.Location ← TEditSelection.InsertionPoint[tSel];
node: Node ← loc.node;
IF node = NIL OR
loc.where=TextNode.NodeItself OR loc.where < count
THEN { GoToPreviousNode; RETURN }; -- try previous node
TEditSelection.MakePointSelection[tSel, [node, loc.where-count]];
TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE];
};
IF count > 0 THEN TEditInputOps.CallWithLocks[DoGoToPreviousChar, read];
};
FindNextWord: PUBLIC PROC [node: Node, start: INT]
RETURNS [nChars: CARDINAL] = {
offset: INT ← start;
size: INT ← TextEdit.Size[node];
Alpha: PROC [index: INT] RETURNS [BOOL] = {
char: XChar ~ TextEdit.FetchChar[node, index];
RETURN [char.set=0 AND RopeEdit.AlphaNumericChar[VAL[char.code]]];
};
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: Node, tSel: TEditDocument.Selection] = {
pos: TextNode.Location ← TEditSelection.InsertionPoint[tSel];
node: Node ← pos.node;
nChars, start, next: INT;
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;
TEditSelection.Deselect[primary];
TextEdit.DeleteText[root, node, start, nChars, TEditInput.currentEvent];
TEditSelection.MakePointSelection[tSel, pos];
TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE];
EXITS Bad => TEditInputOps.EditFailed[];
};
IF count > 0 THEN TEditInputOps.CallWithLocks[DoDeleteNextWord];
};
GoToNextWord: PUBLIC PROC [count: INT ← 1] = {
DoGoToNextWord: PROC [root: Node, tSel: TEditDocument.Selection] = {
pos: TextNode.Location ← TEditSelection.InsertionPoint[tSel];
node: Node ← pos.node;
size, next: INT;
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;
TEditSelection.MakePointSelection[tSel, [node, next]];
TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE];
};
IF count > 0 THEN TEditInputOps.CallWithLocks[DoGoToNextWord, read];
};
GoToNextNode: PUBLIC PROC [count: INT ← 1] = {
DoGoToNextNode: PROC [root: Node, tSel: TEditDocument.Selection] = {
pos: TextNode.Location ← TEditSelection.InsertionPoint[tSel];
node: Node ← pos.node;
FOR garbage: INT IN [0..count) DO
IF (node ← TextNode.StepForward[node])=NIL THEN { TEditInputOps.EditFailed[]; RETURN };
ENDLOOP;
TEditSelection.MakePointSelection[tSel, [node, 0]];
TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE];
};
IF count > 0 THEN TEditInputOps.CallWithLocks[DoGoToNextNode, read];
};
GoToPreviousNode: PUBLIC PROC [count: INT ← 1] = {
DoGoToPreviousNode: PROC [root: Node, tSel: TEditDocument.Selection] = {
pos: TextNode.Location ← TEditSelection.InsertionPoint[tSel];
node: 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;
TEditSelection.MakePointSelection[tSel, [node, TextEdit.Size[node]]];
TEditRefresh.ScrollToEndOfSel[tSel.viewer, TRUE];
EXITS Bad => TEditInputOps.EditFailed[];
};
IF count > 0 THEN TEditInputOps.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: Node, tSel: TEditDocument.Selection] = {
resLen: INT;
caret: TextNode.Location;
dest: Node;
looks: TextLooks.Looks ← TEditSelection.pSel.looks;
date: ROPEIO.PutR[[time[BasicTime.Now[]]]];
dateLen: INT ← Rope.Index[date, 0, ", "];
IF dateLen < date.Size[] THEN dateLen ← Rope.Index[date, dateLen+2, " "];
IF TEditSelection.pSel.pendingDelete THEN TEditInputOps.DoPendingDelete[];
caret ← TEditSelection.InsertionPoint[TEditSelection.pSel];
IF (dest ← caret.node)=NIL THEN GOTO Bad;
TEditSelection.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;
TEditSelection.MakeSelection[selection: primary, new: tSel];
EXITS Bad => TEditInputOps.EditFailed[];
};
TEditInputOps.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: Node, tSel: TEditDocument.Selection] = {
leftEnd, rightEnd: TextNode.Location;
leftNode, rightNode: Node;
l, r: INT;
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;
TEditSelection.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;
TEditSelection.MakeSelection[selection: primary, new: tSel];
EXITS Bad => TEditInputOps.EditFailed[];
};
TEditInputOps.CallWithLocks[DoInsertBrackets];
};
End: ERROR = CODE; -- private; for use in SelectMatchingBrackets
SelectMatchingBrackets: PUBLIC PROC [left, right: CHAR] = {
IF NOT DoSelectMatchingBrackets[left, right] THEN TEditInputOps.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: Node;
node: Node;
GetPreviousNode: PROC RETURNS [n: 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: 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: Node, tSel: TEditDocument.Selection] = {
leftEnd, rightEnd: TextNode.Location;
nest: CARDINAL ← 0;
loc: INT;
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 TEditSelection.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;
TEditSelection.SetSelLooks[tSel];
TEditSelection.MakeSelection[selection: primary, new: tSel];
found ← TRUE;
EXITS NoMatch => found ← FALSE;
};
TEditInputOps.CallWithLocks[DoSelect, read];
};
CallWithPrimarySelectionLocked: PROC [who: ROPE, action: PROC [TEditDocument.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 TEditInputOps.EditFailed["No next viewer."];
};
DoNextViewer: PUBLIC PROC [forward: BOOL] RETURNS [found: BOOLFALSE] = {
Viewer: TYPE ~ ViewerClasses.Viewer;
DoNextViewerAction: PROC [PSel: TEditDocument.Selection] = {
thisViewer: Viewer ~ TEditSelection.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 NOT found AND NOT wentToEnd THEN NextViewer[next];
};
DoFindPlaceholders: PUBLIC PROC [next, gotoend: BOOL,
startBoundaryNode, endBoundaryNode: Node ← NIL,
startBoundaryOffset: INT ← 0,
endBoundaryOffset: INTLAST[INT]]
RETURNS [found, wenttoend: BOOL] = {
DoFind: PROC [root: Node, tSel: TEditDocument.Selection] = {
finder: TreeFind.Finder ← TreeFind.CreateFromRope[IF next THEN "" ELSE ""];
from: TextNode.Location;
where: Node;
at, atEnd, start: INT;
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;
TEditOps.RememberCurrentPosition[tSel.viewer];
TEditSelection.MakePointSelection[tSel, loc];
TEditRefresh.ScrollToEndOfSel[tSel.viewer, FALSE]
}
};
};
from ← TEditSelection.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 };
TEditSelection.MakePointSelection[tSel, [where, IF next THEN at+1 ELSE MAX[at, 0]]];
IF NOT DoSelectMatchingBrackets[', '] THEN { Failed[]; RETURN };
TEditSelection.Copy[source: TEditSelection.pSel, dest: tSel];
tSel.insertion ← before;
tSel.pendingDelete ← TRUE;
TEditOps.RememberCurrentPosition[tSel.viewer];
tSel.looks ← TextEdit.FetchLooks[ -- take looks from first char after the 
tSel.start.pos.node, tSel.start.pos.where+1];
TEditSelection.MakeSelection[tSel, primary];
TEditInput.CloseEvent[];
TEditRefresh.ScrollToEndOfSel[tSel.viewer, FALSE];
};
TEditInputOps.CallWithLocks[DoFind, read];
};
TEditOps Implementation
GetSelContents: PUBLIC PROC RETURNS [contents: ROPE] = {
RETURN[IF TEditSelection.pSel.viewer=NIL THEN NIL ELSE NARROW[TEditSelection.pSel.viewer.class.get[TEditSelection.pSel.viewer, $SelChars]]]
} ;
GetSelData: PUBLIC PROC [primary: BOOLEANTRUE] RETURNS [data: TEditDocument.Selection] = {
RETURN [IF primary THEN TEditSelection.pSel ELSE TEditSelection.sSel]
};
AutoScroll: PUBLIC PROC [tryToGlitch: BOOLEANTRUE] = {
TEditScrolling.AutoScroll[tryToGlitch: tryToGlitch] -- scroll to selection
};
GetTextContents: PUBLIC PROC [viewer: ViewerClasses.Viewer] RETURNS [contents: ROPE] = { RETURN[NARROW[viewer.class.get[viewer]]] };
SetTextContents: PUBLIC PROC [viewer: ViewerClasses.Viewer, contents: ROPE] = { viewer.class.set[viewer, contents] };
END.