TEditMiscOpsImpl.mesa
Copyright Ó 1985, 1987, 1988, 1991, 1992 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, March 23, 1989 2:13:01 pm PST
Russ Atkinson (RRA) January 23, 1986 0:10:22 am PST
Willie-s, February 13, 1991 3:43 pm PST
Doug Wyatt, March 13, 1992 2:13 pm PST
DIRECTORY
AbbrevExpand USING [Expand, Load],
Ascii USING [CR, LF, Lower],
Atom USING [GetPName, MakeAtom],
BasicTime USING [Now],
Char,
CharOps,
EditSpan USING [ChangeCaps],
MessageWindow USING [Append, Blink],
NodeStyleOps USING [ReloadStyle, StyleNameForNode],
NodeStyleWorks USING [ForEachAttachedStyle],
PFS USING [Error],
Rope USING [Concat, Fetch, FromChar, FromProc, IsEmpty, ROPE, Size, Substr],
RopeReader USING [FreeRopeReader, Get, GetIndex, GetRope, GetRopeReader, Ref, SetPosition],
TBase,
TEditDocument USING [Selection, SelectionId],
TEditInput USING [CloseEvent, currentEvent, Interpret],
TEditInputOps USING [CallWithBothLocked, CallWithLocks, CheckReadonly, Delete, DoPendingDelete, EditFailed, FindPlaceholders, 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 [ChangeStyle, DeleteText, FetchChar, FetchLooks, GetFormat, GetNewlineDelimiter, PutFormat, ReplaceByRope, Size],
TextEditBogus USING [GetRope],
TextLooks USING [Looks],
TextNode USING [FirstChild, LastLocWithin, Parent, Root, StepBackward, StepForward],
Tioga,
ViewerClasses USING [Viewer],
ViewerOps USING [PaintViewer],
ViewerTools USING [SetSelection];
TEditMiscOpsImpl: CEDAR PROGRAM
IMPORTS AbbrevExpand, Ascii, Atom, BasicTime, Char, CharOps, EditSpan, MessageWindow, NodeStyleOps, NodeStyleWorks, PFS, Rope, RopeReader, TBase, TEditInput, TEditInputOps, TEditLocks, TEditOps, TEditRefresh, TEditScrolling, TEditSelection, TEditTouchup, TextEdit, TextEditBogus, TextNode, ViewerOps, ViewerTools
EXPORTS TEditInputOps, TEditOps
= BEGIN
ROPE: TYPE ~ Rope.ROPE;
Node: TYPE ~ Tioga.Node;
Location: TYPE ~ Tioga.Location;
NodeItself: INT ~ Tioga.NodeItself;
Looks: TYPE ~ Tioga.Looks;
noLooks: Looks ~ Tioga.noLooks;
Selection: TYPE ~ TEditDocument.Selection;
Capitalise: PUBLIC PROC [flavor: Tioga.CapChange] = {
DoCapitalise: PROC [root: Node, tSel: 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]
};
MungeChar: PROC [proc: TBase.CaretProc] ~ {
DoMungeChar: PROC [root: Node, tSel: Selection] = {
caret: Location ~ TEditSelection.InsertionPoint[tSel];
ok: BOOL ¬ FALSE;
IF caret.node#NIL AND caret.where#NodeItself THEN {
resultStart, resultLen: INT ¬ 0;
TEditSelection.Deselect[];
[ok, resultStart, resultLen] ¬ proc[node: caret.node, caret: caret.where,
event: TEditInput.currentEvent];
IF ok THEN TEditSelection.MakePointSelection[tSel, [caret.node, resultStart+resultLen]]
ELSE TEditSelection.MakeSelection[tSel];
};
IF NOT ok THEN TEditInputOps.EditFailed[];
};
TEditInputOps.CallWithLocks[DoMungeChar];
};
MakeControlCharacter: PUBLIC PROC ~ {
MungeChar[TBase.MakeControlChar];
};
UnMakeControlCharacter: PUBLIC PROC ~ {
MungeChar[TBase.UnMakeControlChar];
};
MakeOctalCharacter: PUBLIC PROC ~ {
MungeChar[TBase.MakeOctalChar];
};
UnMakeOctalCharacter: PUBLIC PROC ~ {
MungeChar[TBase.UnMakeOctalChar];
};
GetWord: PROC RETURNS [word: ROPE] = {
tSel: Selection;
start: INT;
pos: Location;
node: Node;
nChars: CARDINAL ¬ 0;
TEditSelection.CaretAfterSelection;
pos ¬ TEditSelection.InsertionPoint[];
IF (node ¬ pos.node)=NIL THEN RETURN [NIL];
IF pos.where = NodeItself THEN RETURN [NIL];
start ¬ pos.where-1;
WHILE start>=0 DO
c: Char.XCHAR ~ TextEdit.FetchChar[node, start];
IF NOT CharOps.XAlphaNumeric[c] THEN EXIT;
start ¬ start - 1; nChars ¬ nChars + 1;
ENDLOOP;
IF nChars = 0 THEN RETURN [NIL];
start ¬ pos.where-nChars;
word ¬ Rope.Substr[TextEditBogus.GetRope[node], 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: 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 !
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: Selection] = {
name: ROPE ¬ GetWord[];
IF name=NIL THEN { TEditInputOps.EditFailed[]; RETURN };
SetFormatName[name]
};
TEditInputOps.CallWithLocks[DoSetFormat]
};
GetFormat: PUBLIC PROC = {
DoGetFormat: PROC [root: Node, tSel: Selection] = {
name: ROPE;
format: ATOM ¬ TextEdit.GetFormat[TEditSelection.InsertionPoint[].node];
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: Selection ¬ IF target=primary THEN TEditSelection.pSel ELSE TEditSelection.sSel;
srcSel: Selection ¬ IF target=primary THEN TEditSelection.sSel ELSE TEditSelection.pSel;
DoTransFormat: PROC [sourceRoot, destRoot: Node, tSel, srcSel, targetSel: Selection] = {
targetFormat, srcFormat: ATOM;
targetNode, srcNode: Node;
srcNode ¬ IF srcSel.insertion=before THEN srcSel.start.pos.node ELSE srcSel.end.pos.node;
srcFormat ¬ TextEdit.GetFormat[srcNode];
targetNode ¬ IF targetSel.insertion=before THEN targetSel.start.pos.node
ELSE targetSel.end.pos.node;
targetFormat ¬ TextEdit.GetFormat[targetNode];
TEditSelection.Copy[source: targetSel, dest: TEditSelection.oldSel]; -- save for Repeat's
TBase.SetFormat[node1: targetSel.start.pos.node, node2: targetSel.end.pos.node,
format: srcFormat, event: TEditInput.currentEvent];
TBase.SetFormat[node1: srcSel.start.pos.node, node2: srcSel.end.pos.node,
format: targetFormat, event: TEditInput.currentEvent];
TEditSelection.MakeSelection[IF target=primary THEN targetSel ELSE srcSel, primary]
};
TEditInputOps.CallWithBothLocked[DoTransFormat, targetSel, srcSel, write]
};
CopyFormat: PUBLIC PROC [target: TEditDocument.SelectionId ¬ primary] = {
targetSel: Selection ¬ IF target=primary THEN TEditSelection.pSel ELSE TEditSelection.sSel;
srcSel: Selection ¬ IF target=primary THEN TEditSelection.sSel ELSE TEditSelection.pSel;
DoCopyFormat: PROC [sourceRoot, destRoot: Node, tSel, srcSel, targetSel: Selection] = {
srcNode: Node ¬
IF srcSel.insertion=before THEN srcSel.start.pos.node ELSE srcSel.end.pos.node;
format: ATOM ¬ TextEdit.GetFormat[srcNode];
TEditSelection.Copy[source: srcSel, dest: TEditSelection.oldSel]; -- save for Repeat's
TBase.SetFormat[node1: targetSel.start.pos.node, node2: targetSel.end.pos.node,
format: format, event: TEditInput.currentEvent];
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.PutFormat[node, format, TEditInput.currentEvent];
TEditLocks.Unlock[root];
IF lockSel THEN TEditSelection.UnlockSel[primary]
}
};
SetCommentProp: PUBLIC PROC [flag: BOOLEAN] = {
DoSetCommentProp: PROC [root: Node, tSel: Selection] = {
TBase.SetComment[node1: tSel.start.pos.node, node2: tSel.end.pos.node,
comment: flag, event: TEditInput.currentEvent];
};
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: Selection] = {
pos: Location;
node: Node;
offset: INT;
done, keyDeterminesDict: BOOLEAN ¬ FALSE;
clearedMessageWindow: BOOLEAN ¬ FALSE;
keyStart, keyLen, resultLen: INT ¬ 0;
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[TextEditBogus.GetRope[node], 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, TextEditBogus.GetRope[node], 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[dictName ! PFS.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] = {
InsertRope[Rope.FromChar[char]];
DoInsertChar: PROC [root: Node, tSel: Selection] = {
pos: 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.ReplaceByRope[root: root,
dest: pos.node, start: pos.where, len: 0,
rope: Rope.FromChar[char], 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: Selection] = {
pos: Location;
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];
pos.where ¬ pos.where + TextEdit.ReplaceByRope[root: root,
dest: pos.node, start: pos.where, len: 0,
rope: rope, looks: looks, charSet: 0, charProps: NIL,
event: TEditInput.currentEvent].resultLen;
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: Selection] = {
pos: 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 ¬ TextEditBogus.GetRope[node];
start ¬ lim ¬ MAX[0, MIN[pos.where, Rope.Size[rope]]];
WHILE start > 0 AND NOT (SELECT Rope.Fetch[rope, start-1] FROM Ascii.CR, Ascii.LF => TRUE, ENDCASE => FALSE) DO start ¬ start-1; ENDLOOP;
end ¬ start;
WHILE end < lim AND CharOps.Blank[Rope.Fetch[rope, end]] DO
end ¬ end+1;
ENDLOOP;
TEditInputOps.InsertRope[Rope.Concat[TextEdit.GetNewlineDelimiter[root], Rope.Substr[rope, start, end-start]]];
};
TEditInputOps.CallWithLocks[DoInsertLineBreak];
};
DeleteNextChar: PUBLIC PROC [count: INT ¬ 1] = {
DoDeleteNextChar: PROC [root: Node, tSel: Selection] = {
flush: Location ¬ TEditSelection.InsertionPoint[tSel];
node: Node ¬ flush.node;
IF node = NIL THEN GOTO Bad;
IF flush.where=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: Selection] = {
loc: Location ¬ TEditSelection.InsertionPoint[tSel];
node: Node ¬ loc.node;
IF node = NIL OR
loc.where=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: Selection] = {
loc: Location ¬ TEditSelection.InsertionPoint[tSel];
node: Node ¬ loc.node;
IF node = NIL OR
loc.where=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] = {
RETURN[CharOps.XAlphaNumeric[TextEdit.FetchChar[node, index]]];
};
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: Selection] = {
pos: Location ¬ TEditSelection.InsertionPoint[tSel];
node: Node ¬ pos.node;
nChars, start, next: INT;
IF (start ¬ pos.where) = NodeItself THEN GOTO Bad;
IF node=NIL THEN GOTO Bad;
next ¬ start;
THROUGH [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: Selection] = {
pos: Location ¬ TEditSelection.InsertionPoint[tSel];
node: Node ¬ pos.node;
size, next: INT;
IF node=NIL OR
(next ¬ pos.where)=NodeItself THEN { -- try next node
GoToNextNode; RETURN
};
size ¬ TextEdit.Size[node];
THROUGH [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: Selection] = {
pos: Location ¬ TEditSelection.InsertionPoint[tSel];
node: Node ¬ pos.node;
THROUGH [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: Selection] = {
pos: Location ¬ TEditSelection.InsertionPoint[tSel];
node: Node ¬ pos.node;
THROUGH [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];
};
InsertTime: PUBLIC PROC = {
DoInsertTime: PROC [root: Node, tSel: Selection] = {
resultStart, resultLen, dateLen: INT;
caret: Location;
looks: TextLooks.Looks ¬ TEditSelection.pSel.looks;
IF TEditSelection.pSel.pendingDelete THEN TEditInputOps.DoPendingDelete[];
caret ¬ TEditSelection.InsertionPoint[TEditSelection.pSel];
TEditSelection.Deselect[primary];
[resultStart: resultStart, resultLen: resultLen, dateLen: dateLen] ¬ TBase.InsertTime[
node: caret.node, index: caret.where, gmt: BasicTime.Now[],
looks: looks, event: TEditInput.currentEvent];
CharSelectionFromLocations[tSel,
[caret.node, resultStart+dateLen], [caret.node, resultStart+resultLen]];
tSel.insertion ¬ after;
tSel.pendingDelete ¬ FALSE;
TEditSelection.MakeSelection[selection: primary, new: tSel];
};
TEditInputOps.CallWithLocks[DoInsertTime];
};
LocationsFromSelection: PROC [tSel: Selection] RETURNS [loc1, loc2: Location] ~ {
loc1 ¬ tSel.start.pos; loc2 ¬ tSel.end.pos;
IF loc1.node=NIL OR loc2.node=NIL THEN RETURN[[NIL, 0], [NIL, 0]];
IF loc1.where=NodeItself THEN loc1.where ¬ 0;
IF tSel.granularity=point THEN loc2 ¬ loc1
ELSE IF loc2.where=NodeItself THEN loc2.where ¬ TextEdit.Size[loc2.node]
ELSE loc2.where ¬ loc2.where+1;
};
CharSelectionFromLocations: PROC [tSel: Selection, loc1, loc2: Location] ~ {
IF loc1.node=loc2.node AND loc1.where=loc2.where
THEN PointSelectionFromLocation[tSel, loc1]
ELSE {
tSel.start.pos ¬ loc1;
tSel.end.pos ¬ [loc2.node, loc2.where-1];
tSel.granularity ¬ char;
};
};
PointSelectionFromLocation: PROC [tSel: Selection, loc: Location] ~ {
tSel.start.pos ¬ tSel.end.pos ¬ loc;
tSel.granularity ¬ point;
};
InsertBrackets: PUBLIC PROC [left, right: CHAR] = {
InsertBracketRopes[Rope.FromChar[left], Rope.FromChar[right]];
};
InsertBracketRopes: PROC [rope1, rope2: ROPE, includeInSelection: BOOL ¬ FALSE] ~ {
insert rope1 before selection, rope2 after selection
DoInsertBracketRopes: PROC [root: Node, tSel: Selection] = {
loc1, loc2: Location;
[loc1, loc2] ¬ LocationsFromSelection[tSel];
IF loc1.node=NIL THEN GOTO Bad;
TEditSelection.Deselect[primary];
[result1: loc1, result2: loc2] ¬ TBase.InsertBrackets[
loc1: loc1, loc2: loc2, rope1: rope1, rope2: rope2,
looks: tSel.looks, props: NIL, includeInResult: includeInSelection,
event: TEditInput.currentEvent];
CharSelectionFromLocations[tSel, loc1, loc2];
tSel.pendingDelete ¬ FALSE;
TEditSelection.MakeSelection[selection: primary, new: tSel];
EXITS Bad => TEditInputOps.EditFailed[];
};
TEditInputOps.CallWithLocks[DoInsertBracketRopes];
};
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 ¬ FALSE] ~ {
extend selection until includes matching left and right brackets
DoSelect: PROC [root: Node, tSel: Selection] ~ {
loc1, loc2: Location;
[loc1, loc2] ¬ LocationsFromSelection[tSel];
[found, loc1, loc2] ¬ TBase.ExpandToBrackets[
char1: Char.Widen[left], char2: Char.Widen[right],
loc1: loc1, loc2: loc2];
IF found THEN {
CharSelectionFromLocations[tSel, loc1, loc2];
TEditSelection.SetSelLooks[tSel];
TEditSelection.MakeSelection[tSel];
};
};
TEditInputOps.CallWithLocks[DoSelect, read];
};
CallWithPrimarySelectionLocked: PROC [who: ROPE, action: PROC [Selection]] = {
TEditSelection.LockSel[primary, who];
action[TEditSelection.pSel ! UNWIND => TEditSelection.UnlockSel[primary]];
TEditSelection.UnlockSel[primary];
};
NextViewer: PUBLIC PROC [forward: BOOL] = {
IF NOT DoNextViewer[forward] THEN TEditInputOps.EditFailed["No next viewer."];
};
DoNextViewer: PUBLIC PROC [forward: BOOL] RETURNS [found: BOOL ¬ FALSE] = {
Viewer: TYPE ~ ViewerClasses.Viewer;
DoNextViewerAction: PROC [PSel: 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];
};
TryToGetLooks: PROC [node: Node, index: INT, default: Looks] RETURNS [Looks] ~ {
RETURN[IF index IN[0..TextEdit.Size[node])
THEN TextEdit.FetchLooks[node, index] ELSE default];
};
DoFindPlaceholders: PUBLIC PROC [next, gotoend: BOOL,
startBoundaryNode, endBoundaryNode: Node ¬ NIL,
startBoundaryOffset: INT ¬ 0, endBoundaryOffset: INT ¬ LAST[INT]]
RETURNS
[found, wenttoend: BOOL ¬ FALSE] = {
DoFind: PROC [root: Node, tSel: Selection] = {
direction: TBase.Direction ~ (IF next THEN forward ELSE backward);
from: Location ¬ TEditSelection.InsertionPoint[tSel];
start: INT ¬ from.where;
result1, result2: Location;
SELECT direction FROM
forward => IF tSel.insertion=before AND tSel.granularity#point
AND start<TextEdit.Size[from.node] THEN start ¬ start+1;
backward => IF tSel.insertion=after AND start>0 THEN start ¬ start-1;
ENDCASE;
[found, result1, result2] ¬ TBase.FindBrackets[direction: direction,
char1: Char.Widen['], char2: Char.Widen['], loc: [from.node, start],
bound1: [startBoundaryNode, startBoundaryOffset],
bound2: [endBoundaryNode, endBoundaryOffset],
includeInResult: TRUE];
IF found THEN {
TEditInput.CloseEvent[];
CharSelectionFromLocations[tSel, result1, result2];
tSel.insertion ¬ before;
tSel.pendingDelete ¬ TRUE;
tSel.looks ¬ TryToGetLooks[result1.node, result1.where+1, noLooks]; -- char after 
}
ELSE IF gotoend THEN {
end: Location ~ SELECT direction FROM
forward => TextNode.LastLocWithin[root],
backward => [TextNode.FirstChild[root], 0],
ENDCASE
=> ERROR;
IF from#end OR tSel.granularity#point THEN {
PointSelectionFromLocation[tSel, end];
wenttoend ¬ TRUE;
};
};
IF found OR wenttoend THEN {
TEditOps.RememberCurrentPosition[tSel.viewer];
TEditSelection.MakeSelection[tSel, primary];
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: BOOLEAN ¬ TRUE] RETURNS [data: Selection] = {
RETURN [IF primary THEN TEditSelection.pSel ELSE TEditSelection.sSel]
};
AutoScroll: PUBLIC PROC [tryToGlitch: BOOLEAN ¬ TRUE] = {
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.