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];
=
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: ATOM ← IF 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: ATOM ← IF 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: BOOLEAN ← FALSE;
clearedMessageWindow: BOOLEAN ← FALSE;
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: 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: Node, tSel: TEditDocument.Selection] = {
resLen: INT;
caret: TextNode.Location;
dest: Node;
looks: TextLooks.Looks ← TEditSelection.pSel.looks;
date: ROPE ← IO.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:
BOOL ←
FALSE] = {
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: INT ← LAST[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];
};