EditToolSearchImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Michael Plass, March 28, 1985 9:01:55 am PST
Doug Wyatt, May 3, 1985 11:46:50 am PDT
Russ Atkinson (RRA) January 23, 1986 0:57:33 am PST
DIRECTORY
Buttons USING [Button, ButtonProc, SetDisplayStyle],
EditToolBuilder USING [BuildButton, BuildDataFieldPair, BuildLabel, BuildPair, BuildTriple, DataFieldButton, GetDataNode, HGap, ToMiddle, ToNext],
EditToolPrivate USING [anywhere, ChangeState, CycleTriple, DoButton, FixPSel, Info, mainToolInfo, nodes, Register, ReportPatternError, SavePSel, words],
EditSpanSupport USING [CopySpan],
Labels USING [Set],
Menus USING [MenuProc],
MessageWindow USING [Append, Blink, Clear],
NodeProps USING [DoSpecs],
Rope USING [Concat, ROPE],
RunReader USING [Backwards, FreeRunReader, Get, GetRunReader, Ref, SetPosition],
TEditDocument USING [Selection],
TEditInput USING [CloseEvent, CommandProc],
TEditOps USING [GetSelData, SetTextContents],
TEditSelection USING [LockSel, UnlockSel],
TextEdit USING [ChangeFormat, PutProp, Size],
TextFind USING [Finder, MalformedPattern],
TextLooks USING [Looks, noLooks, Runs],
TextLooksSupport USING [LooksAND],
TextNode USING [Ref, RefTextNode, Root, MakeNodeSpan],
TiogaOps USING [CreateGeneralPattern, Finder, MalformedPattern, Pattern, Ref, SearchDir, SelectionSearch],
TreeFind USING [Finder],
ViewerClasses USING [Viewer],
ViewerOps USING [AddProp, BlinkDisplay, CreateViewer],
ViewerSpecs USING [openRightWidth],
ViewerTools USING [InhibitUserEdits];
EditToolSearchImpl: CEDAR PROGRAM
IMPORTS Buttons, EditSpanSupport, EditToolBuilder, EditToolPrivate, Labels, MessageWindow, NodeProps, Rope, RunReader, TEditOps, TextEdit, TextFind, TextLooksSupport, TextNode, TEditInput, TEditSelection, TiogaOps, ViewerOps, ViewerSpecs, ViewerTools
EXPORTS EditToolPrivate
= { OPEN EditToolPrivate, EditToolBuilder;
searchForwardAtom: LIST OF REF = Register[$SearchForward,SearchForwardOp];
SearchForwardOp: TEditInput.CommandProc = { Search[forwards,mainToolInfo] };
searchAnywhereAtom: LIST OF REF = Register[$SearchAnywhere,SearchAnywhereOp];
SearchAnywhereOp: TEditInput.CommandProc = { Search[anywhere,mainToolInfo] };
searchBackwardsAtom: LIST OF REF = Register[$SearchBackwards,SearchBackwardsOp];
SearchBackwardsOp: TEditInput.CommandProc = { Search[backwards,mainToolInfo] };
DoSearchMenuButton: PUBLIC Menus.MenuProc = {
SELECT mouseButton FROM
red => DoButton[searchForwardAtom];
yellow => DoButton[searchAnywhereAtom];
blue => DoButton[searchBackwardsAtom];
ENDCASE => ERROR;
};
CheckPSel: PUBLIC PROC [pSel: TEditDocument.Selection, typescriptOK: BOOLTRUE] RETURNS [ok: BOOL] = {
OPEN MessageWindow;
IF pSel#NIL AND pSel.viewer#NIL AND
(pSel.viewer.class.flavor=$Text OR
(typescriptOK AND pSel.viewer.class.flavor=$Typescript))
THEN RETURN [TRUE];
Append["Please make a text selection.",TRUE];
Blink[];
RETURN [FALSE];
};
GetPatternNode: PUBLIC PROC [info: Info] RETURNS [pattern: TextNode.RefTextNode] = {
OPEN info;
IF ((pattern ← GetDataNode[targetArg])=NIL OR TextEdit.Size[pattern]=0) AND (~ignoreLooks OR ~ignoreText) THEN {
OPEN MessageWindow;
Append["Please enter a search pattern in the \"target\" field.",TRUE];
Blink[]; RETURN [NIL];
};
};
Search: PUBLIC PROC [whichDir: TiogaOps.SearchDir, info: Info] = TRUSTED {
IF ~TrySearch[whichDir, info]
THEN {
MessageWindow.Append["Not found.", TRUE];
ViewerOps.BlinkDisplay[];
}
ELSE MessageWindow.Clear[];
};
TrySearch: PUBLIC PROC [whichDir: TiogaOps.SearchDir, info: Info] RETURNS [found: BOOL] = {
OPEN info;
pattern: TextNode.RefTextNode;
pSel: TEditDocument.Selection;
searchPattern: TiogaOps.Pattern;
IF ignoreText AND ignoreLooks AND ignoreFormat AND ignoreStyle AND ignoreComment THEN {
OPEN MessageWindow;
Append["Pick one or more of text/looks/format/style/comment to match.", TRUE];
Blink[];
RETURN;
};
TEditSelection.LockSel[primary, "TrySearch"];
{
ENABLE UNWIND => TEditSelection.UnlockSel[primary];
FixPSel;
pSel ← TEditOps.GetSelData[];
IF ~CheckPSel[pSel] OR (pattern ← GetPatternNode[info])=NIL THEN {
TEditSelection.UnlockSel[primary];
RETURN;
};
TRUSTED {
searchPattern ← TiogaOps.CreateGeneralPattern[LOOPHOLE[pattern],
~ignoreText, ~ignoreLooks, ~ignoreFormat, ~ignoreStyle, ~ignoreComment,
~ignoreCase, literal, searchWhere=words, ~looksExact, searchWhere=nodes
! TextFind.MalformedPattern => { ReportPatternError[ec]; GOTO Quit };
TiogaOps.MalformedPattern => {
RRA: This stuff is cloned from EditToolMiscImpl.ReportPatternError. Unfortunately, the types are real close, but not exactly the same.
msg: Rope.ROPE = SELECT ec FROM
toobig => "Pattern too long",
endquote => "Pattern ends with quote (')",
endtilda => "Pattern ends with tilde (~)",
boundary => "Pattern has | inside rather than at beginning or end",
missingNameEnd => "Pattern has < without matching >",
unmatchedNameEnd => "Pattern has > without previous <",
ENDCASE => "Pattern error (consult CedarSupport^)";
MessageWindow.Append[msg, TRUE];
MessageWindow.Blink[];
GOTO Quit};
]};
TRUSTED {
need to save this for named subpatterns
finder ← LOOPHOLE[searchPattern.finder, TreeFind.Finder];
};
found ← TiogaOps.SelectionSearch[searchPattern, whichDir, interrupt];
IF NOT found THEN GOTO Quit;
TEditInput.CloseEvent[];
IF info.varNode # NIL THEN info.varNode.next ← NIL;
Break circularity with root node, assuming this is just one node.
info.varNode ← IF info.literal THEN NIL ELSE EditSpanSupport.CopySpan[TextNode.MakeNodeSpan[pSel.start.pos.node, pSel.start.pos.node]].start.node;
Saved in case of named subpatterns.
EXITS Quit => {};
};
TEditSelection.UnlockSel[primary];
};
Extend: PUBLIC PROC [info: Info, forward: BOOL, searchLooks: TextLooks.Looks, where: TextNode.RefTextNode, at, atEnd: INT, last: TextNode.Ref ← NIL, lastLen: INTINT.LAST] RETURNS [newAt, newAtEnd: INT] = {
OPEN info;
runrdr: RunReader.Ref ← RunReader.GetRunReader[];
looks: TextLooks.Looks;
runLen: INT;
runs: TextLooks.Runs ← where.runs;
IF forward THEN { -- extend toward end of node
size: INT ← 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];
};
BuildSearchButtons: PUBLIC PROC [info: Info] = {
BuildSearchChoiceEntries[info]; ToNext[info.layout];
BuildCaseEntry[info]; ToMiddle[info.layout];
BuildWhereEntry[info]; ToNext[info.layout];
BuildLitOrPatternEntry[info]; ToMiddle[info.layout];
BuildLooksMatchEntry[info];
};
equalLooks: Rope.ROPE = "Equal as Looks Test";
subsetLooks: Rope.ROPE = "Subset as Looks Test";
equalLooksAtom: LIST OF REF = Register[$EqualLooksTest,EqualLooksTestOp];
subsetLooksAtom: LIST OF REF = Register[$SubsetLooksTest,SubsetLooksTestOp];
LooksMatchButton: Buttons.ButtonProc = {
ChangeState[mainToolInfo.looksExact, equalLooksAtom,subsetLooksAtom];
};
EqualLooksTestOp: TEditInput.CommandProc = {
EqualLooksTest[mainToolInfo];
};
EqualLooksTest: PROC [info: Info] = {
OPEN info;
looksExact ← TRUE;
Labels.Set[looksMatchLabel,equalLooks];
};
SubsetLooksTestOp: TEditInput.CommandProc = {
SubsetLooksTest[mainToolInfo];
};
SubsetLooksTest: PROC [info: Info] = {
OPEN info;
looksExact ← FALSE;
Labels.Set[looksMatchLabel,subsetLooks];
};
BuildLooksMatchEntry: PROC [info: Info] = {
OPEN info;
looksExact ← FALSE;
[looksMatchLabel,] ← BuildPair[layout,LooksMatchButton,looksExact,equalLooks,subsetLooks,info];
};
--------------------------
BuildSearchChoiceEntries: PROC [info: Info] = {
OPEN info;
BuildEntry: PROC [name: Rope.ROPE, proc: Buttons.ButtonProc, state: BOOL] RETURNS [new: Buttons.Button] = {
new ← BuildButton[layout, name, proc, info, FALSE, FALSE];
ViewerOps.AddProp[new, $DisableMBFeedback, $DisableMBFeedback];
SetButton[new, state, FALSE];
};
[] ← BuildLabel[layout, "Pick one or more: "];
HGap[layout];
textButton ← BuildEntry["Text", MatchTextButton, ignoreText ← FALSE];
looksButton ← BuildEntry["Looks", MatchLooksButton, ignoreLooks ← TRUE];
formatButton ← BuildEntry["Format", MatchFormatButton, ignoreFormat ← TRUE];
styleButton ← BuildEntry["Style", MatchStyleButton, ignoreStyle ← TRUE];
commentButton ← BuildEntry["Comment", MatchCommentButton, ignoreComment ← TRUE];
};
SetButton: PROC [viewer: Buttons.Button, ignore: BOOL, paint: BOOLTRUE] = {
Buttons.SetDisplayStyle[viewer,
IF ignore THEN $BlackOnWhite ELSE $WhiteOnBlack, paint];
};
--------------------------
matchTextAtom: LIST OF REF = Register[$MatchText,MatchTextOp];
ignoreTextAtom: LIST OF REF = Register[$IgnoreText,IgnoreTextOp];
MatchTextButton: Buttons.ButtonProc = {
ChangeState[mainToolInfo.ignoreText, ignoreTextAtom, matchTextAtom];
};
MatchTextOp: TEditInput.CommandProc = { MatchText[mainToolInfo] };
MatchText: PROC [info: Info] = { OPEN info;
SetButton[textButton, ignoreText ← FALSE];
};
IgnoreTextOp: TEditInput.CommandProc = { IgnoreText[mainToolInfo] };
IgnoreText: PROC [info: Info] = { OPEN info;
SetButton[textButton, ignoreText ← TRUE];
};
--------------------------
matchLooksAtom: LIST OF REF = Register[$MatchLooks,MatchLooksOp];
ignoreLooksAtom: LIST OF REF = Register[$IgnoreLooks,IgnoreLooksOp];
MatchLooksButton: Buttons.ButtonProc = {
ChangeState[mainToolInfo.ignoreLooks, ignoreLooksAtom, matchLooksAtom];
};
MatchLooksOp: TEditInput.CommandProc = {
MatchLooks[mainToolInfo];
};
MatchLooks: PROC [info: Info] = {
OPEN info;
SetButton[looksButton, ignoreLooks ← FALSE];
};
IgnoreLooksOp: TEditInput.CommandProc = {
IgnoreLooks[mainToolInfo];
};
IgnoreLooks: PROC [info: Info] = {
OPEN info;
SetButton[looksButton, ignoreLooks ← TRUE];
};
--------------------------
matchFormatAtom: LIST OF REF = Register[$MatchFormat,MatchFormatOp];
ignoreFormatAtom: LIST OF REF = Register[$IgnoreFormat,IgnoreFormatOp];
MatchFormatButton: Buttons.ButtonProc = {
ChangeState[mainToolInfo.ignoreFormat, ignoreFormatAtom,matchFormatAtom]
};
MatchFormatOp: TEditInput.CommandProc = {
MatchFormat[mainToolInfo];
};
MatchFormat: PROC [info: Info] = {
OPEN info;
SetButton[formatButton, ignoreFormat ← FALSE];
};
IgnoreFormatOp: TEditInput.CommandProc = {
IgnoreFormat[mainToolInfo];
};
IgnoreFormat: PROC [info: Info] = {
OPEN info;
SetButton[formatButton, ignoreFormat ← TRUE];
};
matchStyleAtom: LIST OF REF = Register[$MatchStyle,MatchStyleOp];
ignoreStyleAtom: LIST OF REF = Register[$IgnoreStyle,IgnoreStyleOp];
MatchStyleButton: Buttons.ButtonProc = {
ChangeState[mainToolInfo.ignoreStyle,
ignoreStyleAtom,matchStyleAtom] };
MatchStyleOp: TEditInput.CommandProc = {
MatchStyle[mainToolInfo] };
MatchStyle: PROC [info: Info] = {
OPEN info;
SetButton[styleButton, ignoreStyle ← FALSE] };
IgnoreStyleOp: TEditInput.CommandProc = {
IgnoreStyle[mainToolInfo] };
IgnoreStyle: PROC [info: Info] = {
OPEN info;
SetButton[styleButton, ignoreStyle ← TRUE] };
matchCommentAtom: LIST OF REF = Register[$MatchComment,MatchCommentOp];
ignoreCommentAtom: LIST OF REF = Register[$IgnoreComment,IgnoreCommentOp];
MatchCommentButton: Buttons.ButtonProc = {
ChangeState[mainToolInfo.ignoreComment,
ignoreCommentAtom,matchCommentAtom] };
MatchCommentOp: TEditInput.CommandProc = {
MatchComment[mainToolInfo] };
MatchComment: PROC [info: Info] = {
OPEN info;
SetButton[commentButton, ignoreComment ← FALSE] };
IgnoreCommentOp: TEditInput.CommandProc = {
IgnoreComment[mainToolInfo] };
IgnoreComment: PROC [info: Info] = {
OPEN info;
SetButton[commentButton, ignoreComment ← TRUE] };
matchingLiterally: Rope.ROPE = "Match Literally";
matchingAsPattern: Rope.ROPE = "Match as Pattern";
matchLitAtom: LIST OF REF = Register[$MatchLiterally,MatchLitOp];
matchPatternAtom: LIST OF REF = Register[$MatchPattern,MatchPatternOp];
LiteralButton: Buttons.ButtonProc = {
ChangeState[mainToolInfo.literal, matchLitAtom,matchPatternAtom];
};
MatchLitOp: TEditInput.CommandProc = {
MatchLit[mainToolInfo];
};
MatchLit: PROC [info: Info] = {
OPEN info;
literal ← TRUE;
Labels.Set[literalLabel,matchingLiterally];
};
MatchPatternOp: TEditInput.CommandProc = {
MatchPattern[mainToolInfo];
};
MatchPattern: PROC [info: Info] = {
OPEN info;
literal ← FALSE;
Labels.Set[literalLabel,matchingAsPattern];
};
BuildLitOrPatternEntry: PROC [info: Info] = {
OPEN info;
literal ← TRUE;
[literalLabel,] ← BuildPair[layout,LiteralButton,literal,matchingLiterally,matchingAsPattern,info];
};
numDocRopes: NAT = 18;
docRopes: REF ARRAY [0..numDocRopes) OF Rope.ROPE
NEW[ARRAY [0..numDocRopes) OF Rope.ROPE];
BuildPatternDocEntry: PUBLIC PROC [info: Info] = {
OPEN info.layout;
lines: CARDINAL = 25;
rope: Rope.ROPE;
arg: ViewerClasses.Viewer ← ViewerOps.CreateViewer[flavor: $Text, info: [parent: container,
border: FALSE, scrollable: TRUE,
wx: entryLeft, wy: heightSoFar,
ww: ViewerSpecs.openRightWidth-entryLeft-5,
wh: entryHeight*lines], paint: FALSE];
heightSoFar ← heightSoFar + entryHeight*lines;
FOR i:NAT IN [0..numDocRopes) DO
rope ← Rope.Concat[rope, docRopes[i]];
ENDLOOP;
TEditOps.SetTextContents[arg, rope];
};
numMiniDocRopes: NAT = 4;
miniDocRopes: REF ARRAY [0..numMiniDocRopes) OF Rope.ROPE
NEW[ARRAY [0..numMiniDocRopes) OF Rope.ROPE];
miniDocStyle: Rope.ROPE ~ "
BeginStyle
(mini) \"for small comment\" {
 \"TimesRoman\" family
 8 pt size
 9 pt leading
 10 pt topIndent
 0 pt bottomIndent
 0 leftIndent
 5 pt rightIndent
 } StyleRule
EndStyle
";
BuildMiniPatternDocEntry: PUBLIC PROC [info: Info] = {
OPEN info.layout;
lines: CARDINAL = 3;
rope: Rope.ROPE;
where: TextNode.RefTextNode;
height: CARDINAL = numMiniDocRopes*10+3;
arg: ViewerClasses.Viewer ← ViewerOps.CreateViewer[flavor: $Text, info: [parent: container,
wx: entryLeft, wy: heightSoFar,
ww: ViewerSpecs.openRightWidth-entryLeft-5,
wh: height, border: FALSE, scrollable: FALSE], paint: FALSE];
heightSoFar ← heightSoFar + height;
FOR i:NAT IN [0..numMiniDocRopes) DO
rope ← Rope.Concat[rope, miniDocRopes[i]];
ENDLOOP;
TEditOps.SetTextContents[arg, rope];
where ← GetDataNode[arg];
TextEdit.PutProp[node: TextNode.Root[where], name: $StyleDef,
value: NodeProps.DoSpecs[$StyleDef, miniDocStyle]];
TextEdit.ChangeFormat[where, $mini];
ViewerTools.InhibitUserEdits[arg];
};
--------------------------
ignoringCase: Rope.ROPE = "Ignore Case";
matchingCase: Rope.ROPE = "Match Case";
ignoreCaseAtom: LIST OF REF = Register[$IgnoreCase,IgnoreCaseOp];
matchCaseAtom: LIST OF REF = Register[$MatchCase,MatchCaseOp];
CaseButton: Buttons.ButtonProc = {
ChangeState[mainToolInfo.ignoreCase, ignoreCaseAtom,matchCaseAtom];
};
IgnoreCaseOp: TEditInput.CommandProc = {
IgnoreCase[mainToolInfo];
};
IgnoreCase: PROC [info: Info] = {
OPEN info;
ignoreCase ← TRUE;
Labels.Set[caseLabel,ignoringCase];
};
MatchCaseOp: TEditInput.CommandProc = {
MatchCase[mainToolInfo];
};
MatchCase: PROC [info: Info] = {
OPEN info;
ignoreCase ← FALSE;
Labels.Set[caseLabel,matchingCase];
};
BuildCaseEntry: PROC [info: Info] = {
OPEN info;
ignoreCase ← FALSE;
[caseLabel,] ← BuildPair[layout,CaseButton,ignoreCase,ignoringCase,matchingCase,info];
};
matchingWords: Rope.ROPE = "Match Words Only";
matchingAnywhere: Rope.ROPE = "Match Anywhere";
matchingNodes: Rope.ROPE = "Match Entire Nodes Only";
matchWordsAtom: LIST OF REF = Register[$MatchWords,MatchWordsOp];
matchAnywhereAtom: LIST OF REF = Register[$MatchAnywhere,MatchAnywhereOp];
matchNodesAtom: LIST OF REF = Register[$MatchNodes,MatchNodesOp];
WhereButton: Buttons.ButtonProc = {
CycleTriple[mainToolInfo.searchWhere,
matchAnywhereAtom,matchWordsAtom,matchNodesAtom] };
MatchWordsOp: TEditInput.CommandProc = { MatchWords[mainToolInfo] };
MatchWords: PROC [info: Info] = {
OPEN info;
searchWhere ← words;
Labels.Set[wordLabel,matchingWords];
};
MatchNodesOp: TEditInput.CommandProc = { MatchNodes[mainToolInfo] };
MatchNodes: PROC [info: Info] = {
OPEN info;
searchWhere ← nodes;
Labels.Set[wordLabel,matchingNodes];
};
MatchAnywhereOp: TEditInput.CommandProc = {
MatchAnywhere[mainToolInfo];
};
MatchAnywhere: PROC [info: Info] = {
OPEN info;
searchWhere ← anywhere;
Labels.Set[wordLabel,matchingAnywhere];
};
BuildWhereEntry: PROC [info: Info] = {
OPEN info;
searchWhere ← anywhere;
[wordLabel,] ← BuildTriple[layout, WhereButton, searchWhere, matchingAnywhere, matchingWords, matchingNodes, info];
};
--------------------------
targetArgAtom: LIST OF REF = Register[$SearchFor,TargetArgOp];
clearTargetArgAtom: LIST OF REF = Register[$ClearSearchFor,ClearTargetArgOp];
TargetButton: Buttons.ButtonProc = {
DoButton[targetArgAtom, clearTargetArgAtom, mouseButton=red];
};
TargetArgOp: TEditInput.CommandProc = {
TargetArg[mainToolInfo];
};
TargetArg: PROC [info: Info] = {
SavePSel; DataFieldButton[info.targetArg,FALSE];
};
ClearTargetArgOp: TEditInput.CommandProc = {
ClearTargetArg[mainToolInfo];
};
ClearTargetArg: PROC [info: Info] = {
SavePSel; DataFieldButton[info.targetArg,TRUE];
};
BuildTargetEntry: PUBLIC PROC [info: Info] = {
OPEN info;
[,targetArg] ← BuildDataFieldPair[layout, "Target:", TargetButton, info, 2];
};
--------------------------
docRopes[0] ← "The following special symbols may be used in search patterns:
";
docRopes[1] ← " ' Match the next character in the pattern exactly.
";
docRopes[2] ← " ~ Match any character except the next one in the pattern.
";
docRopes[3] ← " # Match any single character.
";
docRopes[4] ← " * Match any sequence of characters.
";
docRopes[5] ← " @ Match any single alphanumeric character (letter or digit).
";
docRopes[6] ← " & Match any sequence of alphanumeric characters.
";
docRopes[7] ← " ~@ Match any single non-alphanumeric character.
";
docRopes[8] ← " ~& Match any sequence of non-alphanumeric characters.
";
docRopes[9] ← " % Match any single blank character.
";
docRopes[10] ← " $ Match any sequence of blank characters.
";
docRopes[11] ← " ~% Match any single non-blank character.
";
docRopes[12] ← " ~$ Match any sequence of non-blank characters.
";
docRopes[13] ← " | Match start or end of node.
";
docRopes[14] ← " { Mark start of resulting selection.
";
docRopes[15] ← " } Mark end of resulting selection.
";
docRopes[16] ← " < Mark start of named subpattern.
";
docRopes[17] ← " > Mark end of named subpattern.
";
miniDocRopes[0] ← "single character: # any; @ alpha; ~@ nonalpha; % blank; ~% nonblank;
";
miniDocRopes[1] ← "min sequence: * any; & alpha; ~& nonalpha; $ blank; ~$ nonblank;
";
miniDocRopes[2] ← "max sequence: ** any; && alpha; ~&~& nonalpha; $$ blank; ~$~$ nonblank;
";
miniDocRopes[3] ← "miscellaneous: 'x = use \"x\"; ~x = not x; { } bounds selection; < > named subpattern
";
}...