-- EditTool.mesa
-- Edited by Paxton on 11-Dec-81 13:04:10

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

EditTool: PROGRAM

IMPORTS Buttons, Convert, Inline, IOStream, Labels, List, Menus, MessageWindow, NodeAddrs, Rope, RunReader,
	Runtime, TEditInputOps, TEditOps, TextEdit, TextFind, TextLooksSupport, TextNode,
	TreeFind, UserTerminal, ViewerClasses, ViewerMenus, ViewerOps =

{ OPEN ViewerClasses;

Event: TYPE = UndoEvent.Ref;

entryHeight: CARDINAL = 15;
entryVSpace: CARDINAL = 5;
entryLeft: CARDINAL ← 5;
gapSize: CARDINAL = 5;
heightSoFar: CARDINAL ← entryVSpace*2;

container: ViewerClasses.Viewer;
containerName: Rope.Ref =" Edit Tool   ";
	
BuildContainer: PROC = {
	container ← ViewerOps.CreateViewer[flavor: $Container, name: containerName,
		iconic: TRUE, attributes: right, paint: TRUE];
	container.menu ← Menus.CreateMenu[];
	Menus.InsertMenuEntry[container.menu, "Destroy", ViewerMenus.Destroy];
	Menus.InsertMenuEntry[container.menu, "<-->", ViewerMenus.Move];
	Menus.InsertMenuEntry[container.menu, "Grow", ViewerMenus.Grow];
	Menus.InsertMenuEntry[container.menu, "Close", ViewerMenus.Close];
	};

----------------------------
forwardButton: Buttons.Button;	-- the "Search Forward!" button
backwardsButton: Buttons.Button;	-- the "Search Backwards!" button

SearchForward: Buttons.ButtonProc = { Search[TRUE] };

SearchBackwards: Buttons.ButtonProc = { Search[FALSE] };

tSel: TEditDocument.Selection = NEW[TEditDocument.SelectionRec];

CheckPSel: PROC [pSel: TEditDocument.Selection] RETURNS [ok: BOOLEAN] = {
	IF pSel=NIL OR pSel.viewer=NIL OR pSel.viewer.class.flavor#$Text THEN { OPEN MessageWindow;
		Append["Please make a text selection.",TRUE];
		Blink[]; RETURN [FALSE] };
	RETURN [TRUE] };

GetPatternNode: PROC RETURNS [pattern: TextNode.RefTextNode] = {
	IF (pattern ← GetDataNode[targetArg])=NIL OR
		TextEdit.Size[pattern]=0 THEN { OPEN MessageWindow;
			Append["Please enter a search pattern in the Target field.",TRUE];
			Blink[]; RETURN [NIL] }};

Search: PROC [forward: BOOLEAN] = { OPEN TreeFind;
	finder: Finder;
	found: BOOLEAN;
	where: TextNode.RefTextNode;
	pattern: TextNode.RefTextNode;
	at, atEnd: TextNode.Offset;
	pSel: TEditDocument.Selection = TEditOps.GetSelData[];
	ignoreLooks, lit: BOOLEAN;
	searchLooks: TextLooks.Looks;
	
	IF CheckPSel[pSel]=FALSE OR (pattern ← GetPatternNode[])=NIL THEN RETURN;
	[pattern,ignoreLooks,lit,searchLooks] ← GetLooksAndPatternInfo[pattern];
	
	{ 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 };
	
	finder ← TreeFind.Create[pattern,lit,word,ignoreLooks,ignoreCase];
	
	IF forward THEN
		[found,where,at,atEnd,,] ←
			Try[finder: finder, first: pSel.end.pos.node, start: pSel.end.pos.where+1,
				looksExact:looksExact]
	ELSE [found,where,at,atEnd,,] ←
		TryBackwards[finder: finder, first: pSel.start.pos.node, len: pSel.start.pos.where,
			looksExact: looksExact];
	};
	
	IF ~found OR where=NIL THEN { UserTerminal.BlinkDisplay[]; RETURN };
	
	IF looksChoice=looksOnly AND ~word THEN
		[at,atEnd] ← Extend[forward,searchLooks,where,at,atEnd];

	tSel.start.pos ← [where,at];
	tSel.end.pos ← tSel.clickPoint ← [where,MAX[0,atEnd-1]];
	tSel.granularity ← char;
	tSel.viewer ← pSel.viewer;
	tSel.data ← pSel.data;
	tSel.insertion ← after;
	TEditOps.SetSelData[tSel];
	TEditOps.CloseRepeatSequence[];
	TEditOps.AutoScroll[FALSE];
	EXITS Quit => RETURN;
	};

Extend: PROC
	[forward: BOOLEAN, searchLooks: TextLooks.Looks,
	where: TextNode.RefTextNode, at, atEnd: TextNode.Offset,
	last: TextNode.Ref ← NIL, lastLen: TextNode.Offset ← TextNode.MaxLen]
	RETURNS [newAt, newAtEnd: TextNode.Offset] = {
	runrdr: RunReader.Ref ← RunReader.GetRunReader[];
	looks: TextLooks.Looks;
	runLen: TextNode.Offset;
	runs: TextLooks.Runs ← where.runs;
	IF forward THEN { -- extend toward end of node
		size: TextNode.Offset ← TextEdit.Size[where];
		IF atEnd=size OR size=0 THEN RETURN [at,atEnd];
		lastLen ← IF where=last THEN MIN[size,lastLen] ELSE size;
		RunReader.SetPosition[runrdr,runs,atEnd];
		WHILE atEnd < lastLen DO
			IF runs=NIL THEN { runLen ← size-atEnd; looks ← TextLooks.noLooks }
			ELSE [runLen,looks] ← RunReader.Get[runrdr];
			IF ~looksExact THEN looks ← TextLooksSupport.LooksAND[looks,searchLooks];
			IF searchLooks # looks THEN EXIT;
			atEnd ← atEnd+runLen;
			ENDLOOP;
		RunReader.FreeRunReader[runrdr];
		RETURN [at,MIN[atEnd,lastLen]] };
	IF at=0 THEN RETURN [at,atEnd];
	RunReader.SetPosition[runrdr,runs,at];
	WHILE at > 0 DO
		IF runs=NIL THEN { runLen ← at; looks ← TextLooks.noLooks }
		ELSE [runLen,looks] ← RunReader.Backwards[runrdr];
		IF ~looksExact THEN looks ← TextLooksSupport.LooksAND[looks,searchLooks];
		IF searchLooks # looks THEN EXIT;
		at ← at-runLen;
		ENDLOOP;
	RunReader.FreeRunReader[runrdr];
	RETURN [at,atEnd] };

BuildSearchEntries: PROC = {
	forwardButton ← Buttons.Create["Search Forward!", SearchForward,
		entryLeft, heightSoFar, 0, 0,
		NIL, FALSE, container, FALSE];
	forwardButton.border ← FALSE;
	backwardsButton ← Buttons.Create["Search Backwards!", SearchBackwards,
		entryLeft+forwardButton.ww+gapSize*2, heightSoFar, 0, 0,
		NIL, FALSE, container, FALSE];
	backwardsButton.border ← FALSE;
	heightSoFar ← heightSoFar + entryHeight + entryVSpace;
	};

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

FlipState: PROC [label: Labels.Label, flag: BOOLEAN, l1, l2: Rope.Ref]
	RETURNS [new: BOOLEAN] = {
	new ← ~flag;
	Labels.Set[label, IF new THEN l1 ELSE l2] };

BuildPair: PROC [proc: Buttons.ButtonProc, flag: BOOLEAN, l1, l2: Rope.Ref]
	RETURNS [label: Labels.Label, button: Buttons.Button] = {
	fudge: INTEGER = 1;
	xTemp: INTEGER;
	w: INTEGER = MAX[
		Menus.ComputeStringInfo[Rope.ToString[l1]].width,
		Menus.ComputeStringInfo[Rope.ToString[l2]].width];
	button ← Buttons.Create[">> ", proc,
		entryLeft, heightSoFar, 0, 0,
		NIL, FALSE, container, FALSE];
	xTemp ← entryLeft+button.ww+gapSize;
	label ← Labels.Create[IF flag THEN l1 ELSE l2, container,
		xTemp, heightSoFar+fudge, w+15, entryHeight, FALSE];
	label.border ← FALSE;
	heightSoFar ← heightSoFar + entryHeight + entryVSpace;
	};

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

CycleTriple: PROC [label: Labels.Label, state: [0..2], l0, l1, l2: Rope.Ref]
	RETURNS [new: [0..2]] = {
	nextState: ARRAY [0..2] OF [0..2] = [1,2,0];
	nextRope: ARRAY [0..2] OF Rope.Ref = [l0,l1,l2];
	new ← nextState[state];
	Labels.Set[label,nextRope[new]] };

BuildTriple: PROC [proc: Buttons.ButtonProc, state: [0..2], l0, l1, l2: Rope.Ref]
	RETURNS [label: Labels.Label, button: Buttons.Button] = {
	fudge: INTEGER = 1;
	w: INTEGER = MAX[
		Menus.ComputeStringInfo[Rope.ToString[l0]].width,
		Menus.ComputeStringInfo[Rope.ToString[l1]].width,
		Menus.ComputeStringInfo[Rope.ToString[l2]].width];
	labelRopes: ARRAY [0..2] OF Rope.Ref = [l0,l1,l2];
	xTemp: INTEGER;
	button ← Buttons.Create[">> ", proc,
		entryLeft, heightSoFar, 0, 0,
		NIL, FALSE, container, FALSE];
	xTemp ← entryLeft+button.ww+gapSize;
	label ← Labels.Create[labelRopes[state], container,
		xTemp, heightSoFar+fudge, w+15, entryHeight, FALSE];
	label.border ← FALSE;
	heightSoFar ← heightSoFar + entryHeight + entryVSpace;
	};


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

BuildSearchButtons: PROC = {
	startHeight: CARDINAL ← heightSoFar;
	initLeft: CARDINAL = entryLeft;
	BuildCaseEntry[]; BuildWordEntry[];
	entryLeft ← (openRightWidth-scrollBarW-40)/2;
	heightSoFar ← startHeight;
	BuildLooksEntry[]; BuildLooksMatchEntry[];
	entryLeft ← initLeft;
	BuildLitOrPatternEntry[] };
	
----------------------------

----------------------------
looksMatchButton: Buttons.Button;
looksMatchLabel: Labels.Label;
looksExact: BOOLEAN ← FALSE;
equalLooks: Rope.Ref = "Equal as Looks Test";
subsetLooks: Rope.Ref = "Subset as Looks Test";

LooksMatchButton: Buttons.ButtonProc = {
	looksExact ← FlipState[looksMatchLabel,looksExact,equalLooks,subsetLooks] };

BuildLooksMatchEntry: PROC = {
	[looksMatchLabel,looksMatchButton] ←
		BuildPair[LooksMatchButton,looksExact,equalLooks,subsetLooks] };

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

----------------------------
looksButton: Buttons.Button;
looksLabel: Labels.Label;
looksChoice: [0..2] ← textAndLooks;
looksOnly: [0..2] = 0;
textOnly: [0..2] = 1;
textAndLooks: [0..2] = 2;
textOnlyRope: Rope.Ref = "Text Only";
looksOnlyRope: Rope.Ref = "Looks Only";
textAndLooksRope: Rope.Ref = "Text & Looks";

LooksButton: Buttons.ButtonProc = {
	looksChoice ← CycleTriple[looksLabel,looksChoice,
		looksOnlyRope, textOnlyRope, textAndLooksRope] };

BuildLooksEntry: PROC = {
	[looksLabel,looksButton] ←
		BuildTriple[LooksButton,looksChoice,
			looksOnlyRope, textOnlyRope, textAndLooksRope] };

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


----------------------------
literalButton: Buttons.Button;
literalLabel: Labels.Label;
literal: BOOLEAN ← TRUE;
matchingLiterally: Rope.Ref = "Match Literally";
matchingAsPattern: Rope.Ref = "Match as Pattern";

LiteralButton: Buttons.ButtonProc = {
	literal ← FlipState[literalLabel,literal,matchingLiterally,matchingAsPattern] };

BuildLitOrPatternEntry: PROC = {
	[literalLabel,literalButton] ←
		BuildPair[LiteralButton,literal,matchingLiterally,matchingAsPattern] };

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

patternDoc1: Rope.Ref = "# = any char; * = any string; 'x = use x literally;";
patternDoc2: Rope.Ref = "& = any alpha string; ~ = any non-alpha string;";
patternDoc3: Rope.Ref = "@ = any alpha char; ! = any non-alpha char;";
patternDoc4: Rope.Ref = "| = node boundary; { } bounds resulting selection";

BuildPatternDocEntry: PROC = {
	Place: PROC [line: Rope.Ref] = {
		label: Labels.Label ← Labels.Create[line, container,
			entryLeft+gapSize*3, heightSoFar,
			openRightWidth-scrollBarW-5, entryHeight,
			FALSE];
		label.border ← FALSE; heightSoFar ← heightSoFar + entryHeight };
	Place[patternDoc1]; Place[patternDoc2]; Place[patternDoc3]; Place[patternDoc4];
	heightSoFar ← heightSoFar + entryVSpace };

----------------------------
caseButton: Buttons.Button;
caseLabel: Labels.Label;
ignoreCase: BOOLEAN ← FALSE;
ignoringCase: Rope.Ref = "Ignore Case";
matchingCase: Rope.Ref = "Match Case";

CaseButton: Buttons.ButtonProc = {
	ignoreCase ← FlipState[caseLabel,ignoreCase,ignoringCase,matchingCase] };

BuildCaseEntry: PROC = {
	[caseLabel,caseButton] ←
		BuildPair[CaseButton,ignoreCase,ignoringCase,matchingCase] };

----------------------------
wordButton: Buttons.Button;
wordLabel: Labels.Label;
word: BOOLEAN ← FALSE;
matchingWords: Rope.Ref = "Match Words Only";
matchingAnywhere: Rope.Ref = "Match Anywhere";

WordButton: Buttons.ButtonProc = {
	word ← FlipState[wordLabel,word,matchingWords,matchingAnywhere] };

BuildWordEntry: PROC = {
	[wordLabel,wordButton] ←
		BuildPair[WordButton,word,matchingWords,matchingAnywhere] };

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

DataFieldButton: PROC [arg: ViewerClasses.Viewer] = {
	tdd: TEditDocument.TEditDocumentData = NARROW[arg.data];
	TEditOps.SetTextContents[arg, NIL]; -- clear the field
	tSel.start.pos ← tSel.end.pos ← tSel.clickPoint ← [GetDataNode[arg],0];
	tSel.granularity ← char;
	tSel.viewer ← arg;
	tSel.data ← tdd;
	tSel.insertion ← before;
	TEditOps.SetSelData[tSel]; -- place caret in field
	};

BuildDataFieldPair: PROC [buttonRope: Rope.Ref, buttonProc: Buttons.ButtonProc, lines: CARDINAL ← 2]
	RETURNS [button: Buttons.Button, arg: ViewerClasses.Viewer] = {
	xTemp: CARDINAL;
	fudge: CARDINAL = 2;
	button ← Buttons.Create[buttonRope, buttonProc,
		entryLeft, heightSoFar, 0, 0,
		NIL, FALSE, container, FALSE];
	button.border ← FALSE;
	xTemp ← entryLeft+scrollBarW+button.ww;
	arg ← ViewerOps.CreateChild[flavor: $Text, parent: container,
		x: xTemp, y: heightSoFar+fudge,
		w: openRightWidth-xTemp-scrollBarW-5,
		h: entryHeight*lines, paint: FALSE];
	arg.border ← FALSE; arg.scrollable ← TRUE;
	heightSoFar ← heightSoFar + entryHeight*lines + entryVSpace;
	TEditOps.SetTextContents[arg, NIL]; -- clear the field
	};

GetDataNode: PROC [arg: ViewerClasses.Viewer] RETURNS [TextNode.RefTextNode] = {
	tdd: TEditDocument.TEditDocumentData = NARROW[arg.data];
	RETURN [TextNode.NarrowToTextNode[TextNode.FirstChild[tdd.text]]] };	

GetDataLooks: PROC [arg: ViewerClasses.Viewer, name: Rope.Ref]
	RETURNS [looks: TextLooks.Looks] = {
	node: TextNode.RefTextNode ← GetDataNode[arg];
	size: TextNode.Offset = TextEdit.Size[node];
	IF size=0 THEN RETURN [TextLooks.noLooks];
	looks ← TextEdit.FetchLooks[node,0];
	FOR i: TextNode.Offset IN [1..size) DO
		IF TextEdit.FetchLooks[node,i]#looks THEN { OPEN MessageWindow;
			Append[name,TRUE];
			Append[" does not have uniform looks.  Using looks from first char."];
			Blink[]; EXIT };
		ENDLOOP };	

----------------------------
targetButton: Buttons.Button;
targetArg: ViewerClasses.Viewer; -- the viewer holding the target pattern

TargetButton: Buttons.ButtonProc = { DataFieldButton[targetArg] };

BuildTargetEntry: PROC = {
	[targetButton,targetArg] ← BuildDataFieldPair["Search for:", TargetButton] };


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

SourceButton: Buttons.ButtonProc = { DataFieldButton[sourceArg] };

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

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

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

BuildDoItEntries: 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;
	doitButton ← Buttons.Create[replaceRope, DoIt,
		entryLeft+gapSize*4+yesButton.ww+noButton.ww, heightSoFar, 0, 0,
		NIL, FALSE, container, FALSE];
	doitButton.border ← FALSE;
	substituteButton ← Buttons.Create[subsRope, DoSubstitute,
		entryLeft+gapSize*6+yesButton.ww+noButton.ww+doitButton.ww,
		heightSoFar, 0, 0,
		NIL, FALSE, container, FALSE];
	substituteButton.border ← FALSE;
	helpButton ← Buttons.Create["Help!", Help,
		entryLeft+gapSize*8+yesButton.ww+noButton.ww+
			doitButton.ww+substituteButton.ww,
		heightSoFar, 0, 0, NIL, FALSE, container, FALSE];
	helpButton.border ← FALSE;
	heightSoFar ← heightSoFar + entryHeight + entryVSpace;
	};

Help: Buttons.ButtonProc = { OPEN MessageWindow;
	Append["See [Indigo]<Tioga>FindTool.Doc for help.",TRUE];
	Blink[] };

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

OperationButton: Buttons.ButtonProc = {
	doReplace ← FlipState[operationLabel,doReplace,replaceOperation,specifiedOperation];
	Buttons.ReLabel[doitButton, IF doReplace THEN replaceRope ELSE doitRope];
	Buttons.ReLabel[substituteButton, IF doReplace THEN subsRope ELSE doallRope] };

BuildOperationEntry: PROC = {
	[operationLabel,operationButton] ←
		BuildPair[OperationButton,doReplace,replaceOperation,specifiedOperation] };

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

InitCapButton: Buttons.ButtonProc = {
	forceInitCap ← FlipState[initCapLabel,forceInitCap,forcingInitCap,ignoringInitCap] };

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

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

OpsButton: Buttons.ButtonProc = { DataFieldButton[opsArg] };

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

GetOps: PROC RETURNS [list: LIST OF REF ANY] = {
	GetIOStream[];
	{ 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[];
	}};

loadedIOStream: BOOLEAN ← FALSE;

GetIOStream: PROC = {
	IF ~Runtime.IsBound[IOStream.GetRefAny] THEN { OPEN MessageWindow;
		Append["Run IOStreamPackage", TRUE]; Blink };
	};

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

BuildGetAndSetOpsEntries: 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[]] };

ShowOps: PROC [list: LIST OF REF ANY] = {
	GetIOStream[];
	{ 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 };
		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 => {
				Space[]; PutChar[h,'"]; Put[h,rope[x]]; PutChar[h,'"] };
			x: REF TEXT => {
				Space[]; PutChar[h,'"]; Put[h,text[x]]; PutChar[h,'"] };
			ENDCASE;
		ENDLOOP;
	nospace ← TRUE; Space[]; 
	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;
	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;
	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;

ComNumButton: Buttons.ButtonProc = { DataFieldButton[comArg] };

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

----------------------------
----------------------------
DoIt: Buttons.ButtonProc = {
	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 };
	TEditOps.InterpretInput[pSel.viewer, params] };
	
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]] };

DoYes: Buttons.ButtonProc = { DoIt[NIL, NIL]; SearchForward[NIL, NIL] };

DoNo: Buttons.ButtonProc = { SearchForward[NIL, NIL] };

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

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

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

SubsRangeButton: Buttons.ButtonProc = {
	subsRange ← CycleTriple[subsRangeLabel, subsRange,
		withinSelRope, afterSelRope, entireDocRope] };

GetLooksAndPatternInfo: 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 };

DoSubstitute: Buttons.ButtonProc = {
	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[];
	viewer: 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;
	viewer ← 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 ← pSel.viewer;
		tSel.data ← pSel.data;
		tSel.insertion ← after;
		TEditOps.SetSelData[tSel];
		TEditOps.AutoScroll[];
		};
		
	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;
		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];
		MakeSel[where,at,atEnd];
		IF where # lastWhere AND lastWhere # NIL THEN
			NodeAddrs.RemTextAddr[lastWhere,$After];
		lastWhere ← where;
		NodeAddrs.PutTextAddr[where,$After,atEnd];
		TEditOps.InterpretInput[pSel.viewer, 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;
	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 ← viewer;
	tSel.data ← data;
	tSel.insertion ← insertion;
	
	-- repaint
	IF subsRange=withinSel THEN {
		TEditOps.SetSelData[tSel,TRUE,FALSE];
		TEditOps.PaintEdits[pSel] }
	ELSE {	TEditOps.SetSelData[NIL]; -- clear it
		ViewerOps.PaintViewer[viewer];
		TEditOps.SetSelData[tSel] };
	
	-- 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;
	};

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

BuildContainer[];	-- build enclosing viewer

BuildTargetEntry[];
heightSoFar ← heightSoFar + entryVSpace;

BuildSearchEntries[];
heightSoFar ← heightSoFar + entryVSpace;

BuildSearchButtons[];
heightSoFar ← heightSoFar + entryVSpace;

BuildSourceEntry[];
heightSoFar ← heightSoFar + entryVSpace;

BuildDoItEntries[];
heightSoFar ← heightSoFar + entryVSpace;

BuildSubstituteEntry[];

BuildInitCapEntry[];

BuildOperationEntry[];
heightSoFar ← heightSoFar + entryVSpace*2;

BuildOperationField[];
heightSoFar ← heightSoFar + entryVSpace*2;

BuildGetAndSetOpsEntries[];
BuildComNumField[];
heightSoFar ← heightSoFar + entryVSpace*2;

BuildPatternDocEntry[];
heightSoFar ← heightSoFar + entryVSpace;


ViewerOps.SetOpenHeight[container, heightSoFar];

}.

..