-- EditToolSubsImpl.mesa
-- Edited by Paxton on  6-Feb-82 11:56:26

DIRECTORY
  EditToolPrivate,
  Buttons,
  Convert,
  EditNotify,
  Inline,
  IOStream,
  UndoEvent,
  Labels,
  List,
  Menus,
  MessageWindow,
  NodeAddrs,
  Rope,
  RopeEdit,
  RopeReader,
  RunReader,
  TextEdit,
  TextFind,
  TextLooks,
  TextLooksSupport,
  TextNode,
  TEditDocument,
  TEditInputOps,
  TEditOps,
  TreeFind,
  UserTerminal,
  ViewerOps,
  ViewerClasses,
  ViewerMenus;

EditToolSubsImpl: PROGRAM

IMPORTS  EditToolPrivate, Buttons, Convert, Inline, IOStream, Labels, List, MessageWindow, NodeAddrs,
	Rope, TEditInputOps, TEditOps, TextEdit, TextFind, TextNode,
	TreeFind, UserTerminal, ViewerOps
EXPORTS EditToolPrivate =

{ OPEN ViewerClasses, EditToolPrivate;

----------------------------

----------------------------
sourceButton: Buttons.Button;
sourceArg: PUBLIC ViewerClasses.Viewer; -- the viewer holding the source text

sourceArgAtom: ATOM = $EditToolReplaceBy;

SourceButton: Buttons.ButtonProc = {
	IF mainEditTool THEN TEditOps.InterpretAtom[button,sourceArgAtom]
	ELSE [] ← SourceArgOp[] };

SourceArgOp: TEditOps.CommandProc = { DataFieldButton[sourceArg] };

BuildSourceEntry: PUBLIC PROC = {
	[sourceButton,sourceArg] ← BuildDataFieldPair["Replace by:", SourceButton] };

----------------------------

doitButton: Buttons.Button;
yesButton: Buttons.Button;
noButton: Buttons.Button;
substituteButton: Buttons.Button;
replaceRope: Rope.Ref = "Replace!";
doitRope: Rope.Ref = "DoOne!";
subsRope: Rope.Ref = "Substitute!";
doallRope: Rope.Ref = "DoAll!";

BuildDoItEntries: PUBLIC PROC = {
	yesButton ← Buttons.Create["Yes!", DoYes,
		entryLeft, heightSoFar, 0, 0,
		NIL, FALSE, container, FALSE];
	yesButton.border ← FALSE;
	noButton ← Buttons.Create["No!", DoNo,
		entryLeft+gapSize*2+yesButton.ww, heightSoFar, 0, 0,
		NIL, FALSE, container, FALSE];
	noButton.border ← FALSE;
	substituteButton ← Buttons.Create[subsRope, DoSubstitute,
		entryLeft+gapSize*4+yesButton.ww+noButton.ww,
		heightSoFar, 0, 0,
		NIL, TRUE, container, FALSE]; -- notice that we fork Substitute's
	substituteButton.border ← FALSE;
	doitButton ← Buttons.Create[replaceRope, DoIt,
		entryLeft+gapSize*6+yesButton.ww+noButton.ww+substituteButton.ww,
		heightSoFar, 0, 0, NIL, FALSE, container, FALSE];
	doitButton.border ← FALSE;
	heightSoFar ← heightSoFar + entryHeight + entryVSpace;
	};

----------------------------
operationButton: Buttons.Button;
operationLabel: Labels.Label;
doReplace: PUBLIC BOOLEAN ← TRUE;
replaceOperation: Rope.Ref = "Do Replace";
specifiedOperation: Rope.Ref = "Do Operations Specified Below";

doOpsAtom: ATOM = $EditToolDoOps;
doReplaceAtom: ATOM = $EditToolDoReplace;

OperationButton: Buttons.ButtonProc = {
	IF mainEditTool THEN ChangeState[operationLabel,doReplace,doReplaceAtom,doOpsAtom]
	ELSE IF doReplace THEN [] ← DoOpsOp[] ELSE [] ← DoReplaceOp[] };

DoOpsOp: TEditOps.CommandProc = {
	doReplace ← FALSE;
	Labels.Set[operationLabel, specifiedOperation];
	Buttons.ReLabel[doitButton, doitRope];
	Buttons.ReLabel[substituteButton, doallRope] };
	
DoReplaceOp: TEditOps.CommandProc = {
	doReplace ← TRUE;
	Labels.Set[operationLabel, replaceOperation];
	Buttons.ReLabel[doitButton, replaceRope];
	Buttons.ReLabel[substituteButton, subsRope] };
	
BuildOperationEntry: PUBLIC PROC = {
	[operationLabel,operationButton] ←
		BuildPair[OperationButton,doReplace,replaceOperation,specifiedOperation] };

----------------------------
----------------------------
initCapButton: Buttons.Button;
initCapLabel: Labels.Label;
forceInitCap: PUBLIC BOOLEAN ← TRUE;
forcingInitCap: Rope.Ref = "Capitalize like first replaced char";
ignoringInitCap: Rope.Ref = "Don't change replacement capitalization";

forceInitCapAtom: ATOM = $EditToolChangeInitCap;
ignoreInitCapAtom: ATOM = $EditToolLeaveInitCap;

InitCapButton: Buttons.ButtonProc = {
	IF mainEditTool THEN
		ChangeState[initCapLabel,forceInitCap,forceInitCapAtom,ignoreInitCapAtom]
	ELSE IF forceInitCap THEN [] ← LeaveInitCapOp[] ELSE [] ← ChangeInitCapOp[] };

ChangeInitCapOp: TEditOps.CommandProc = {
	forceInitCap ← TRUE;
	Labels.Set[initCapLabel,forcingInitCap] };

LeaveInitCapOp: TEditOps.CommandProc = {
	forceInitCap ← FALSE;
	Labels.Set[initCapLabel,ignoringInitCap] };

BuildInitCapEntry: PUBLIC PROC = {
	[initCapLabel,initCapButton] ←
		BuildPair[InitCapButton,forceInitCap,forcingInitCap,ignoringInitCap] };

----------------------------
opsButton: Buttons.Button;
opsArg: ViewerClasses.Viewer;

opsAtom: ATOM = $EditToolOperations;

OpsButton: Buttons.ButtonProc = {
	IF mainEditTool THEN TEditOps.InterpretAtom[button,opsAtom] 
	ELSE [] ← OpsArgOp[] };

OpsArgOp: TEditOps.CommandProc = { DataFieldButton[opsArg] };

BuildOperationField: PUBLIC PROC = {
	[opsButton,opsArg] ← BuildDataFieldPair["Operations:", OpsButton] };

GetOps: PROC RETURNS [list: LIST OF REF ANY] = { OPEN IOStream;
	rope: Rope.Ref ← TextEdit.GetRope[GetDataNode[opsArg]];
	h: Handle ← CreateInputStreamFromRope[rope];
	item: REF ANY;
	WHILE (item ← GetRefAny[h ! EndOfStream => { item ← NIL; CONTINUE}]) # NIL DO
		WITH item SELECT FROM
			x: ROPE => item ← LOOPHOLE[Rope.ToString[x], REF TEXT]; -- for now must convert ROPE to REF TEXT
			ENDCASE;
		list ← List.Nconc1[list, item];
		ENDLOOP;
	--h.Close[];
	};

----------------------------
----------------------------
getopsButton: Buttons.Button;
setopButton: Buttons.Button;
getopButton: Buttons.Button;

BuildGetAndSetOpsEntries: PUBLIC PROC = {
	getopsButton ← Buttons.Create["GetLastOps!", DoGetOps,
		entryLeft, heightSoFar, 0, 0,
		NIL, FALSE, container, FALSE];
	getopsButton.border ← FALSE;
	setopButton ← Buttons.Create["SetCom!", DoSetCom,
		entryLeft+gapSize*2+getopsButton.ww, heightSoFar, 0, 0,
		NIL, FALSE, container, FALSE];
	setopButton.border ← FALSE;
	getopButton ← Buttons.Create["GetCom!", DoGetCom,
		entryLeft+gapSize*4+getopsButton.ww+setopButton.ww, heightSoFar, 0, 0,
		NIL, FALSE, container, FALSE];
	getopButton.border ← FALSE;
	heightSoFar ← heightSoFar + entryHeight + entryVSpace;
	};

DoGetOps: Buttons.ButtonProc = { ShowOps[TEditOps.GetRepeatSequence[]] };
	-- this cannot be a registered TEdit atom since must reenter tedit monitor to get ops

ShowOps: PROC [list: LIST OF REF ANY] = { OPEN IOStream;
	h: IOStream.Handle ← CreateOutputStreamToRope[];
	doingChars: BOOLEAN ← FALSE;
	nospace: BOOLEAN ← TRUE;
	Space: PROC = {
		IF doingChars THEN { -- end of string
			PutChar[h, '"]; doingChars ← FALSE };
		IF nospace THEN nospace ← FALSE ELSE PutChar[h,' ] };
	AddChar: PROC [c: CHARACTER] = {
		IF ~doingChars THEN { -- start of string
			Space[]; PutChar[h, '"]; doingChars ← TRUE };
		SELECT c FROM
			'', '", '\\ => PutChar[h, '\\];
			ENDCASE;
		PutChar[h, c] };
	{ ENABLE UNWIND => h.Close[];
	FOR l: LIST OF REF ANY ← list, l.rest UNTIL l=NIL DO
		WITH l.first SELECT FROM
			x: ATOM => { Space[]; Put[h,atom[x]] };
			x: REF INT => { Space[]; Put[h,int[x↑]] };
			x: REF CHARACTER => AddChar[x↑];
			x: ROPE => {
				AddC: PROC [c: CHAR] RETURNS [BOOL] = {
					AddChar[c]; RETURN [FALSE] };
				[] ← Rope.Map[base: x, action: AddC] };
			x: REF TEXT => {
				FOR i: NAT IN [0..x.length) DO AddChar[x[i]]; ENDLOOP };
			ENDCASE;
		ENDLOOP;
	IF doingChars THEN PutChar[h, '"];
	TEditOps.SetTextContents[opsArg, GetOutputStreamRope[h]];
	}};

debugCom: BOOLEAN ← FALSE;
SetCom: PROC [num: [0..9]] = {
	list: LIST OF REF ANY ← GetOps[];
	IF debugCom THEN ShowOps[list];
	TEditOps.SetCommand[num,list];
	};

DoSetCom: Buttons.ButtonProc = { OPEN IOStream;
	-- this cannot be a registered TEdit atom since must reenter tedit monitor to get ops
	rope: Rope.Ref ← TextEdit.GetRope[GetDataNode[comArg]];
	h: Handle ← CreateInputStreamFromRope[rope];
	num: INT ← GetInt[h ! Error => GOTO BadNum];
	IF num ~IN [0..9] THEN GOTO BadNum;
	SetCom[Inline.LowHalf[num]];
	EXITS BadNum => { OPEN MessageWindow;
		Append["Enter number from 0 to 9 in Command Number field", TRUE];
		Blink[] } };

DoGetCom: Buttons.ButtonProc = { OPEN IOStream;
	-- this cannot be a registered TEdit atom since must reenter tedit monitor to get ops
	rope: Rope.Ref ← TextEdit.GetRope[GetDataNode[comArg]];
	h: Handle ← CreateInputStreamFromRope[rope];
	num: INT ← GetInt[h ! Error => GOTO BadNum];
	IF num ~IN [0..9] THEN GOTO BadNum;
	ShowOps[TEditOps.GetCommand[Inline.LowHalf[num]]];
	EXITS BadNum => { OPEN MessageWindow;
		Append["Enter number from 0 to 9 in Command Number field", TRUE];
		Blink[] } };


----------------------------
comButton: Buttons.Button;
comArg: ViewerClasses.Viewer;

comNumAtom: ATOM = $EditToolCommandNumber;

ComNumButton: Buttons.ButtonProc = {
	IF mainEditTool THEN TEditOps.InterpretAtom[button,comNumAtom]
	ELSE [] ← DoComNumOp[] };

DoComNumOp: TEditOps.CommandProc = { DataFieldButton[comArg] };

BuildComNumField: PUBLIC PROC = {
	[comButton,comArg] ← BuildDataFieldPair["Command number [0..9]:", ComNumButton, 1];
	TEditOps.SetTextContents[comArg, "1 "]; };

----------------------------
----------------------------
doItAtom: ATOM = $EditToolDoIt;

DoIt: Buttons.ButtonProc = {
	IF mainEditTool THEN TEditOps.InterpretAtom[button,doItAtom]
	ELSE [] ← DoItOp[] };

DoItOp: TEditOps.CommandProc = { 
	pSel: TEditDocument.Selection;
	params: LIST OF REF ANY;
	IF doReplace THEN { DoReplace[]; RETURN };
	IF (pSel ← TEditOps.GetSelData[])=NIL OR pSel.viewer=NIL OR pSel.viewer.class.flavor#$Text OR
		(params ← GetOps[])=NIL THEN { UserTerminal.BlinkDisplay[]; RETURN };
	IF mainEditTool THEN TEditOps.SneakyInterpret[pSel.viewer, params]
	ELSE TEditOps.InterpretInput[pSel.viewer, params] };
	
tSel: PUBLIC TEditDocument.Selection ← NEW[TEditDocument.SelectionRec];

DoReplace: PROC = {
	-- for now do it as a delete followed by a copy
	-- to do the copy, must make source the secondary selection
	pSel: TEditDocument.Selection = TEditOps.GetSelData[];
	tdd: TEditDocument.TEditDocumentData = NARROW[sourceArg.data];
	node: TextNode.RefTextNode ← GetDataNode[sourceArg];
	size: TextNode.Offset;
	runs: TextLooks.Runs;
	initCap: BOOLEAN;
	IF pSel=NIL OR pSel.viewer=NIL OR pSel.viewer.class.flavor#$Text THEN {
		UserTerminal.BlinkDisplay[]; RETURN };
	SELECT looksChoice FROM
		looksOnly => {
			targetLooks: TextLooks.Looks ← GetDataLooks[targetArg,"Target"];
			sourceLooks: TextLooks.Looks ← GetDataLooks[sourceArg,"Source"];
			TEditInputOps.ChangeLooks[sourceLooks,targetLooks];
			RETURN };
		textOnly => IF node#NIL THEN { 
			runs ← node.runs; -- save source looks
			TextEdit.SetLooks[node,GetSelInitLooks[pSel]] };
		textAndLooks => NULL;
		ENDCASE => ERROR;
	initCap ← forceInitCap AND IsSelInitCap[pSel];
	TEditOps.OpenRepeatSequence[];
	TEditInputOps.Delete[];
	IF (size ← TextEdit.Size[node]) = 0 THEN RETURN; -- no source
	tSel.start.pos ← [node,0];
	tSel.end.pos ← tSel.clickPoint ← [node,size];
	tSel.granularity ← char;
	tSel.viewer ← sourceArg;
	tSel.data ← tdd;
	tSel.insertion ← before;
	TEditOps.SetSelData[tSel,FALSE];
	TEditInputOps.Copy[];
	IF looksChoice=textOnly AND node#NIL THEN node.runs ← runs; -- restore source
	IF initCap AND IsSelInitLower[pSel] THEN TEditInputOps.Capitalise[firstCap];
	};

GetSelInitLooks: PROC [pSel: TEditDocument.Selection] RETURNS [TextLooks.Looks] = {
	node: TextNode.RefTextNode ← TextNode.NarrowToTextNode[pSel.start.pos.node];
	loc: TextNode.Offset ← pSel.start.pos.where;
	IF node=NIL OR loc=TextNode.NodeItself OR loc=TextEdit.Size[node]
		THEN RETURN [TextLooks.noLooks];
	RETURN [TextEdit.FetchLooks[node,loc]] };

IsSelInitCap: PROC [pSel: TEditDocument.Selection] RETURNS [BOOLEAN] = {
	node: TextNode.RefTextNode ← TextNode.NarrowToTextNode[pSel.start.pos.node];
	loc: TextNode.Offset ← pSel.start.pos.where;
	IF node=NIL OR loc=TextNode.NodeItself OR loc=TextEdit.Size[node]
		THEN RETURN [FALSE];
	RETURN [TextEdit.FetchChar[node,loc] IN ['A..'Z]] };

IsSelInitLower: PROC [pSel: TEditDocument.Selection] RETURNS [BOOLEAN] = {
	node: TextNode.RefTextNode ← TextNode.NarrowToTextNode[pSel.start.pos.node];
	loc: TextNode.Offset ← pSel.start.pos.where;
	IF node=NIL OR loc=TextNode.NodeItself OR loc=TextEdit.Size[node]
		THEN RETURN [FALSE];
	RETURN [TextEdit.FetchChar[node,loc] IN ['a..'z]] };

doYesAtom: ATOM = $EditToolDoYes;

DoYes: Buttons.ButtonProc = { 
	IF mainEditTool THEN TEditOps.InterpretAtom[button,doYesAtom]
	ELSE [] ← DoYesOp[] };

DoYesOp: TEditOps.CommandProc = { [] ← DoItOp[]; Search[TRUE] };

doNoAtom: ATOM = $EditToolDoNo;

DoNo: Buttons.ButtonProc = {
	IF mainEditTool THEN TEditOps.InterpretAtom[button,doNoAtom]
	ELSE [] ← DoNoOp[] };

DoNoOp: TEditOps.CommandProc = { Search[TRUE] };

----------------------------

subsRangeButton: Buttons.Button;
subsRangeLabel: Labels.Label;
subsRange: PUBLIC [0..2] ← withinSel;
withinSelRope: Rope.Ref = "Within Selection Only";
afterSelRope: Rope.Ref = "After Selection Only";
entireDocRope: Rope.Ref = "In Entire Document";

withinSelAtom: ATOM = $EditToolSubInSel;
afterSelAtom: ATOM = $EditToolSubAfterSel;
entireDocAtom: ATOM = $EditToolSubEntireDoc;

BuildSubstituteEntry: PUBLIC PROC = {
	[subsRangeLabel,subsRangeButton] ← BuildTriple[SubsRangeButton, withinSel,
		withinSelRope, afterSelRope, entireDocRope] };

SubsRangeButton: Buttons.ButtonProc = {
	IF mainEditTool THEN
		CycleTriple[subsRangeLabel, subsRange, withinSelAtom, afterSelAtom, entireDocAtom]
	ELSE SELECT subsRange FROM
		withinSel => [] ← SubsAfterOp[];
		afterSel => [] ← SubsEntireDocOp[];
		entireDoc => [] ← SubsWithinOp[];
		ENDCASE => ERROR };

SubsWithinOp: TEditOps.CommandProc = {
	subsRange ← withinSel;
	Labels.Set[subsRangeLabel,withinSelRope] };

SubsAfterOp: TEditOps.CommandProc = {
	subsRange ← afterSel;
	Labels.Set[subsRangeLabel,afterSelRope] };

SubsEntireDocOp: TEditOps.CommandProc = {
	subsRange ← entireDoc;
	Labels.Set[subsRangeLabel,entireDocRope] };

GetLooksAndPatternInfo: PUBLIC PROC [pattern: TextNode.RefTextNode]
	RETURNS [pat: TextNode.RefTextNode,
		ignoreLooks, lit: BOOLEAN,
		searchLooks: TextLooks.Looks] = {
	pat ← pattern; lit ← literal; searchLooks ← TextLooks.noLooks;
	SELECT looksChoice FROM
		looksOnly => {
			size: TextNode.Offset = TextEdit.Size[pattern];
			searchLooks ← IF size=0 THEN TextLooks.noLooks
				ELSE TextEdit.FetchLooks[pattern,0];
			FOR i: TextNode.Offset IN [1..size) DO
				IF TextEdit.FetchLooks[pattern,i]#searchLooks THEN {
					OPEN MessageWindow;
					Append["Pattern does not have uniform looks.",TRUE];
					Append["  Using looks from first char."];
					Blink[]; EXIT };
				ENDLOOP;
			ignoreLooks ← lit ← FALSE;
			pat ← TextEdit.FromRope["#*"];
			TextEdit.SetLooks[pat,searchLooks] };
		textOnly => ignoreLooks ← TRUE;
		textAndLooks => ignoreLooks ← FALSE;
		ENDCASE => ERROR };

doSubsAtom: ATOM = $EditToolSubstitute;

DoSubstitute: Buttons.ButtonProc = {
	IF mainEditTool THEN TEditOps.InterpretAtom[button,doSubsAtom]
	ELSE [] ← DoSubstituteOp[] };

DoSubstituteOp: TEditOps.CommandProc = {
	finder: TreeFind.Finder;
	first, last, selStart, selEnd: TextNode.Ref;
	firstText, lastText: TextNode.RefTextNode;
	lastWhere: TextNode.RefTextNode;
	pattern: TextNode.RefTextNode;
	start, lastLen: TextNode.Offset;
	count: LONG INTEGER;
	pSel: TEditDocument.Selection = TEditOps.GetSelData[];
	vwr: ViewerClasses.Viewer;
	data: TEditDocument.TEditDocumentData;
	insertion: TEditDocument.BeforeAfter;
	granularity: TEditDocument.SelectionGrain;
	ignoreLooks, lit: BOOLEAN;
	searchLooks: TextLooks.Looks;
	
	IF CheckPSel[pSel]=FALSE OR (pattern ← GetPatternNode[])=NIL THEN RETURN;
	vwr ← pSel.viewer;
	[pattern,ignoreLooks,lit,searchLooks] ← GetLooksAndPatternInfo[pattern];
	
	SELECT subsRange FROM
		withinSel => {
			first ← pSel.start.pos.node;
			last ← pSel.end.pos.node;
			start ← pSel.start.pos.where;
			lastLen ← pSel.end.pos.where };
		afterSel => {
			first ← pSel.end.pos.node;
			last ← NIL;
			start ← pSel.end.pos.where+1;
			lastLen ← 0 };
		entireDoc => {
			first ← TextNode.Root[pSel.start.pos.node];
			last ← NIL;
			start ← 0;
			lastLen ← 0 };
		ENDCASE => ERROR;
	
	data ← pSel.data;
	insertion ← pSel.insertion;
	granularity ← pSel.granularity;
	selStart ← pSel.start.pos.node;
	selEnd ← pSel.end.pos.node;
	
	IF (firstText ← TextNode.NarrowToTextNode[selStart]) # NIL THEN
		NodeAddrs.PutTextAddr[firstText,$Start,pSel.start.pos.where];
	IF (lastText ← TextNode.NarrowToTextNode[selEnd]) # NIL THEN
		NodeAddrs.PutTextAddr[lastText,$End,pSel.end.pos.where];
	
	{ ENABLE TextFind.MalformedPattern => { OPEN MessageWindow;
		Append[SELECT ec FROM
			toobig => "Search pattern is too big",
			endquote => "Search pattern incorrectly ends with quote",
			boundary => "Search pattern incorrectly has | inside rather than at beginning or end",
			ENDCASE => "Error in search pattern",  TRUE];
		Blink[];
		GOTO Quit };
	
	MakeSel: PROC [where: TextNode.RefTextNode, at, atEnd:TextNode.Offset] = {
		tSel.start.pos ← [where,at];
		tSel.end.pos ← [where,MAX[0,atEnd-1]];
		tSel.granularity ← char;
		tSel.viewer ← vwr;
		tSel.data ← pSel.data;
		tSel.insertion ← after;
		TEditOps.SetSelData[tSel];
		TEditOps.AutoScroll[FALSE];
		};
		
	DoSubs: PROC [where: TextNode.RefTextNode, at, atEnd, before, after: TextNode.Offset]
		RETURNS [continue, bumpCount: BOOLEAN, from, delta: TextNode.Offset] = {
		len: TextNode.Offset ← atEnd-at;
		size: TextNode.Offset ← TextEdit.Size[where];
		initCap: BOOLEAN ← forceInitCap AND
			at < size AND TextEdit.FetchChar[where,at] IN ['A..'Z];
		initLooks: TextLooks.Looks;
		IF interrupt THEN { continue ← FALSE; RETURN };
		continue ← bumpCount ← TRUE;
		SELECT looksChoice FROM
			looksOnly => {
				IF looksChoice=looksOnly AND ~word THEN {
					[at,atEnd] ← Extend[TRUE,searchLooks,where,at,atEnd,last,lastLen];
					len ← atEnd-at };
				TextEdit.ChangeLooks[where,targetLooks,sourceLooks,
					at,len,event];
				from ← at+len; delta ← 0; RETURN };
			textOnly => initLooks ← IF at < size THEN
				TextEdit.FetchLooks[where,at] ELSE TextLooks.noLooks;
			textAndLooks => NULL;
			ENDCASE => ERROR;
		--MakeSel[where,at,atEnd];
		[] ← TextEdit.ReplaceByText[where,at,len,
			sourceRope,sourceRuns,sourceStart,sourceLen,event];
		IF initCap AND sourceLen > 0 AND TextEdit.FetchChar[where,at] IN ['a..'z] THEN
			TextEdit.ChangeCaps[where,at,1,firstCap,event];
		IF looksChoice=textOnly THEN
			TextEdit.SetLooks[where,initLooks,at,sourceLen,event];
		--TEditOps.PaintEdits[pSel];
		from ← at+sourceLen; delta ← sourceLen-len };
		
	DoOps: PROC [where: TextNode.RefTextNode, at, atEnd, before, after: TextNode.Offset]
		RETURNS [continue, bumpCount: BOOLEAN, from, delta: TextNode.Offset] = {
		len: TextNode.Offset ← atEnd-at;
		size: TextNode.Offset ← TextEdit.Size[where];
		IF interrupt THEN { continue ← FALSE; RETURN };
		MakeSel[where,at,atEnd];
		IF where # lastWhere AND lastWhere # NIL THEN
			NodeAddrs.RemTextAddr[lastWhere,$After];
		lastWhere ← where;
		NodeAddrs.PutTextAddr[where,$After,atEnd];
		IF mainEditTool THEN TEditOps.SneakyInterpret[vwr, params]
		ELSE TEditOps.InterpretInput[vwr, params];
		delta ← NodeAddrs.GetTextAddr[where,$After]-atEnd;
		from ← atEnd+delta; continue ← bumpCount ← TRUE };
		
	source: TextNode.RefTextNode ← GetDataNode[sourceArg];
	sourceStart, sourceLen, sourceEnd: TextNode.Offset;
	sourceRope: Rope.Ref;
	sourceRuns: TextLooks.Runs;
	targetLooks, sourceLooks: TextLooks.Looks;
	size: TextNode.Offset ← TextEdit.Size[source];
	event: UndoEvent.Ref ← TEditOps.CurrentEvent[];
	params: LIST OF REF ANY;
	interrupt ← FALSE;
	IF source # NIL THEN { sourceRope ← source.rope; sourceRuns ← source.runs };
	sourceStart ← 0; sourceLen ← size; sourceEnd ← sourceStart+sourceLen;
	finder ← TreeFind.Create[pattern,lit,word,ignoreLooks,ignoreCase];
	IF ~doReplace THEN {
		IF (params ← GetOps[])=NIL THEN {
			UserTerminal.BlinkDisplay[]; RETURN }}
	ELSE IF looksChoice=looksOnly THEN {
		targetLooks ← GetDataLooks[targetArg,"Target"];
		sourceLooks ← GetDataLooks[sourceArg,"Source"] };
	TEditOps.OpenRepeatSequence;
	count ← TreeFind.Apply[finder,first,
		IF doReplace THEN DoSubs ELSE DoOps,
		start,last,lastLen,looksExact];
	};
		
	-- update the selection
	tSel.start.pos ← [selStart,
		IF firstText=NIL THEN TextNode.NodeItself
		ELSE NodeAddrs.GetTextAddr[firstText,$Start]];
	tSel.end.pos ← tSel.clickPoint ← [selEnd,
		IF lastText=NIL THEN TextNode.NodeItself
		ELSE NodeAddrs.GetTextAddr[lastText,$End]];
	IF firstText#NIL THEN NodeAddrs.RemTextAddr[firstText,$Start];
	IF lastText#NIL THEN NodeAddrs.RemTextAddr[lastText,$End];
	IF lastWhere#NIL THEN NodeAddrs.RemTextAddr[lastWhere,$After];
	tSel.granularity ← granularity;
	tSel.viewer ← vwr;
	tSel.data ← data;
	tSel.insertion ← insertion;
	
	-- repaint
	IF subsRange=withinSel THEN {
		TEditOps.SetSelData[tSel,TRUE,FALSE];
		TEditOps.PaintEdits[tSel] }
	ELSE {
		TEditOps.SetSelData[NIL]; -- clear it
		ViewerOps.PaintViewer[vwr];
		TEditOps.SetSelData[tSel] };
	IF count > 0 THEN ViewerOps.SetNewVersion[vwr];
	
	-- display the number of substitutions made
	MessageWindow.Append[
		Rope.Concat[Convert.ValueToRope[[signed[count,10]]],
			IF count # 1 THEN " substitutions." ELSE " substitution."], TRUE];
	
	EXITS Quit => RETURN;
	};

RegisterSubs: PUBLIC PROC = {
	TEditOps.Register[doSubsAtom, DoSubstituteOp];
	TEditOps.Register[sourceArgAtom, SourceArgOp];
	TEditOps.Register[opsAtom, OpsArgOp];
	TEditOps.Register[comNumAtom, DoComNumOp];
	TEditOps.Register[doItAtom, DoItOp];
	TEditOps.Register[doYesAtom, DoYesOp];
	TEditOps.Register[doNoAtom, DoNoOp];
	TEditOps.Register[doOpsAtom, DoOpsOp];
	TEditOps.Register[doReplaceAtom, DoReplaceOp];
	TEditOps.Register[forceInitCapAtom, ChangeInitCapOp];
	TEditOps.Register[ignoreInitCapAtom, LeaveInitCapOp];
	TEditOps.Register[withinSelAtom, SubsWithinOp];
	TEditOps.Register[afterSelAtom, SubsAfterOp];
	TEditOps.Register[entireDocAtom, SubsEntireDocOp];
	};

UnRegisterSubs: PUBLIC PROC = {
	TEditOps.UnRegister[doSubsAtom];
	TEditOps.UnRegister[sourceArgAtom];
	TEditOps.UnRegister[opsAtom];
	TEditOps.UnRegister[comNumAtom];
	TEditOps.UnRegister[doItAtom];
	TEditOps.UnRegister[doYesAtom];
	TEditOps.UnRegister[doNoAtom];
	TEditOps.UnRegister[doOpsAtom];
	TEditOps.UnRegister[doReplaceAtom];
	TEditOps.UnRegister[forceInitCapAtom];
	TEditOps.UnRegister[ignoreInitCapAtom];
	TEditOps.UnRegister[withinSelAtom];
	TEditOps.UnRegister[afterSelAtom];
	TEditOps.UnRegister[entireDocAtom];
	};


}...