-- EditToolSearchImpl.mesa
-- Edited by Paxton on  6-Feb-82 12:00:51

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

EditToolSearchImpl: PROGRAM

IMPORTS EditToolPrivate, Buttons, Labels, MessageWindow, RunReader,
	TEditOps, TextEdit, TextFind, TextLooksSupport,
	TreeFind, UserTerminal, ViewerSpecs 
EXPORTS EditToolPrivate =

{ OPEN EditToolPrivate, ViewerSpecs;
----------------------------
forwardButton: Buttons.Button;	-- the "Search Forward!" button
backwardsButton: Buttons.Button;	-- the "Search Backwards!" button

searchForwardAtom: ATOM = $EditToolSearchForward;

SearchForward: PUBLIC Buttons.ButtonProc = {
	IF mainEditTool THEN TEditOps.InterpretAtom[button,searchForwardAtom]
	ELSE [] ← SearchForwardOp[] };

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

searchBackwardsAtom: ATOM = $EditToolSearchBackwards;

SearchBackwards: PUBLIC Buttons.ButtonProc = {
	IF mainEditTool THEN TEditOps.InterpretAtom[button,searchBackwardsAtom]
	ELSE [] ←SearchBackwardsOp[] };

SearchBackwardsOp: TEditOps.CommandProc = { Search[FALSE] };

CheckPSel: PUBLIC 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: PUBLIC 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: PUBLIC 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: PUBLIC 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: PUBLIC 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;
	};

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

BuildSearchButtons: PUBLIC 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: PUBLIC BOOLEAN ← FALSE;
equalLooks: Rope.Ref = "Equal as Looks Test";
subsetLooks: Rope.Ref = "Subset as Looks Test";

equalLooksAtom: ATOM = $EditToolEqualLooksTest;
subsetLooksAtom: ATOM = $EditToolSubsetLooksTest;

LooksMatchButton: Buttons.ButtonProc = {
	IF mainEditTool THEN
		ChangeState[looksMatchLabel,looksExact,equalLooksAtom,subsetLooksAtom]
	ELSE IF looksExact THEN [] ← SubsetLooksTestOp[] ELSE [] ← EqualLooksTestOp[] };

EqualLooksTestOp: TEditOps.CommandProc = {
	looksExact ← TRUE;
	Labels.Set[looksMatchLabel,equalLooks] };

SubsetLooksTestOp: TEditOps.CommandProc = {
	looksExact ← FALSE;
	Labels.Set[looksMatchLabel,subsetLooks] };

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

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

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

textOnlyAtom: ATOM = $EditToolTextOnly;
looksOnlyAtom: ATOM = $EditToolLooksOnly;
textAndLooksAtom: ATOM = $EditToolTextAndLooks;

LooksButton: Buttons.ButtonProc = {
	IF mainEditTool THEN
		CycleTriple[looksLabel,looksChoice, looksOnlyAtom, textOnlyAtom, textAndLooksAtom] 
	ELSE SELECT looksChoice FROM
		textOnly => [] ← LooksOnlyOp[];
		looksOnly => [] ← TextAndLooksOp[];
		textAndLooks => [] ← TextOnlyOp[]; 
		ENDCASE => ERROR };

TextOnlyOp: TEditOps.CommandProc = {
	looksChoice ← textOnly;
	Labels.Set[looksLabel,textOnlyRope] };

LooksOnlyOp: TEditOps.CommandProc = {
	looksChoice ← looksOnly;
	Labels.Set[looksLabel,looksOnlyRope] };

TextAndLooksOp: TEditOps.CommandProc = {
	looksChoice ← textAndLooks;
	Labels.Set[looksLabel,textAndLooksRope] };

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

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


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

matchLitAtom: ATOM = $EditToolMatchLiterally;
matchPatternAtom: ATOM = $EditToolMatchPattern;

LiteralButton: Buttons.ButtonProc = {
	IF mainEditTool THEN
		ChangeState[literalLabel,literal,matchLitAtom,matchPatternAtom]
	ELSE IF literal THEN [] ← MatchPatternOp[] ELSE [] ← MatchLitOp[] };

MatchLitOp: TEditOps.CommandProc = {
	literal ← TRUE;
	Labels.Set[literalLabel,matchingLiterally] };

MatchPatternOp: TEditOps.CommandProc = {
	literal ← FALSE;
	Labels.Set[literalLabel,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: PUBLIC 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: PUBLIC BOOLEAN ← FALSE;
ignoringCase: Rope.Ref = "Ignore Case";
matchingCase: Rope.Ref = "Match Case";

ignoreCaseAtom: ATOM = $EditToolIgnoreCase;
matchCaseAtom: ATOM = $EditToolMatchCase;

CaseButton: Buttons.ButtonProc = {
	IF mainEditTool THEN
		ChangeState[caseLabel,ignoreCase,ignoreCaseAtom,matchCaseAtom]
	ELSE IF ignoreCase THEN [] ← MatchCaseOp[] ELSE [] ← IgnoreCaseOp[] };

IgnoreCaseOp: TEditOps.CommandProc = {
	ignoreCase ← TRUE;
	Labels.Set[caseLabel,ignoringCase] };

MatchCaseOp: TEditOps.CommandProc = {
	ignoreCase ← FALSE;
	Labels.Set[caseLabel,matchingCase] };

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

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

matchWordsAtom: ATOM = $EditToolMatchWords;
matchAnywhereAtom: ATOM = $EditToolMatchAnywhere;

WordButton: Buttons.ButtonProc = {
	IF mainEditTool THEN
		ChangeState[wordLabel,word,matchWordsAtom,matchAnywhereAtom]
	ELSE IF word THEN [] ← MatchAnywhereOp[] ELSE [] ← MatchWordsOp[] };

MatchWordsOp: TEditOps.CommandProc = {
	word ← TRUE;
	Labels.Set[wordLabel,matchingWords] };

MatchAnywhereOp: TEditOps.CommandProc = {
	word ← FALSE;
	Labels.Set[wordLabel,matchingAnywhere] };

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

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

targetArgAtom: ATOM = $EditToolSearchFor;

TargetButton: Buttons.ButtonProc = {
	IF mainEditTool THEN TEditOps.InterpretAtom[button,targetArgAtom]
	ELSE [] ← TargetArgOp[] };

TargetArgOp: TEditOps.CommandProc = { DataFieldButton[targetArg] };

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


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

RegisterSearch: PUBLIC PROC = {
	TEditOps.Register[searchForwardAtom, SearchForwardOp];
	TEditOps.Register[searchBackwardsAtom, SearchBackwardsOp];
	TEditOps.Register[targetArgAtom, TargetArgOp];
	TEditOps.Register[equalLooksAtom, EqualLooksTestOp];
	TEditOps.Register[subsetLooksAtom, SubsetLooksTestOp];
	TEditOps.Register[textOnlyAtom, TextOnlyOp];
	TEditOps.Register[looksOnlyAtom, LooksOnlyOp];
	TEditOps.Register[textAndLooksAtom, TextAndLooksOp];
	TEditOps.Register[matchLitAtom, MatchLitOp];
	TEditOps.Register[matchPatternAtom, MatchPatternOp];
	TEditOps.Register[ignoreCaseAtom, IgnoreCaseOp];
	TEditOps.Register[matchCaseAtom, MatchCaseOp];
	TEditOps.Register[matchWordsAtom, MatchWordsOp];
	TEditOps.Register[matchAnywhereAtom, MatchAnywhereOp];
	};

UnRegisterSearch: PUBLIC PROC = {
	TEditOps.UnRegister[searchForwardAtom];
	TEditOps.UnRegister[searchBackwardsAtom];
	TEditOps.UnRegister[targetArgAtom];
	TEditOps.UnRegister[equalLooksAtom];
	TEditOps.UnRegister[subsetLooksAtom];
	TEditOps.UnRegister[textOnlyAtom];
	TEditOps.UnRegister[looksOnlyAtom];
	TEditOps.UnRegister[textAndLooksAtom];
	TEditOps.UnRegister[matchLitAtom];
	TEditOps.UnRegister[matchPatternAtom];
	TEditOps.UnRegister[ignoreCaseAtom];
	TEditOps.UnRegister[matchCaseAtom];
	TEditOps.UnRegister[matchWordsAtom];
	TEditOps.UnRegister[matchAnywhereAtom];
	};

}...