Text Filter

Post your working scripts, libraries and tools for AHK v1.1 and older
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

08 Apr 2021, 03:57

the script is updated. → viewtopic.php?p=391320#p391320 ← click this link

1, there was certain inaccuracy in "non-word" character(s) handling for the "improvised shorthand" feature, and now got fixed.

2, there was a loophole in the "matches highlighting" under a very specific condition when the search term contains nothing else but only negation(s) (whereas negation is a word/string/pattern which starts with a minus sign "-"), and now got fixed.

that's it for now.
ozzii
Posts: 481
Joined: 30 Oct 2013, 06:04

Re: Text Filter

09 Apr 2021, 03:07

Thank you for the fixes.

Maybe in your post of the code, delete the rtf file because it's not needed anymore. Just a cosmetic thing :D
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

13 Apr 2021, 00:10

planet-seven-e: font size can be changed on the spot.

to increase font size: either hold the {ctrl} key down and roll up the mouse wheel while the mouse pointer is currently on top of the big text/edit box (the one in richedit) i.e. ctrl+wheelup, or press the plus key {+} (or {=}) while the big text/edit box is in focus.

to decrease font size: either hold the {ctrl} key down and roll down the mouse wheel while the mouse pointer is currently on top of the big text/edit box (the one in richedit) i.e. ctrl+wheeldown, or press the minus key {-} while the big text/edit box is in focus.

2021Apr18: planet-seven-f added a feature to glance the committed level(s) and can directly fall back to the one you select. it lets you review brief information about the steps that you've taken all the way to the current level, and may save you some keystrokes if you want to get a leap back to an earlier level at once. just right click on the back button, that's it.
and some minor changes as well.

2021Apr24: planet-seven-g added an overall rightclick menu which should have covered all the features available. (right click on the back button or edit box or window ribbon would show their own menu nonetheless.)

2021May5: planet-seven-h added a treeview gui for "assorted matches". you can find it in the rightclick menu. (it's available there only when "assorted matches" is shown though.) the treeview gui is owned by its caller, that is, the text filter gui. and hence, closing of the text filter gui will also close the treeview gui as well.

the script is updated.

Code: Select all

Version = Planet-Seven-H
SetWorkingDir %A_ScriptDir%
RE_Dll := DllCall("LoadLibrary", "Str", "Msftedit.dll", "Ptr")
OnMessage(0x201, "EventHandler")	;WM_LBUTTONDOWN
OnMessage(0x202, "EventHandler")	;WM_LBUTTONUP
IfExist, tfWork.rtf
	FileDelete, tfWork.rtf
IfExist, tfHelp.rtf
	FileDelete, tfHelp.rtf

HelpPage=
(
{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fnil\fcharset0 Arial;}{\f1\fnil\fcharset0 Consolas;}}
{\colortbl ;\red255\green0\blue0;\red155\green0\blue211;\red0\green77\blue187;\red0\green176\blue80;\red0\green0\blue255;}
{\*\generator Riched20 10.0.19041}\viewkind4\uc1 
\pard\sa200\sl276\slmult1\b\fs36\lang9 H e l p _ P a g e\b0\par
\par
\cf1\b F1\cf0\b0  - to toggle this help page\par
\par
\b Basic syntax by example:\b0\par

\pard\li720\sa200\sl276\slmult1 coexisting words/strings/patterns -unwanted\par
for instance, \cf2\b\f1 d.*d -the and -oo \\ws\cf0\b0\f0\par
(the 5 segments (space separated) above can go all at once, and yet, they can also be entered one by one. that's the so-called multilevel approach, instead.)\par

\pard\sa200\sl276\slmult1\par
\b Feature keys:\b0\par

\pard\li720\sa200\sl276\slmult1\cf3\b Alt+a\cf0\b0  - to show assorted matches and toggle between chronological and alphabetical order.\par
\cf3\b WheelDown\cf0\b0  - to find the next match (downward) from the caret position (given that, the mouse pointer is currently outside both text/edit boxes.)\par
\cf3\b WheelUp\cf0\b0  - to find the next match (upward) from the caret position (given that, the mouse pointer is currently outside both text/edit boxes.)\par
\cf3\b Alt+v\cf0\b0  - to paste clipboard content into the big text/edit box.\par
\cf3\b Ctrl+o\cf0\b0  - to open a file.\par
\cf3\b Ctrl+r\cf0\b0  - to reload the file.\par
\cf3\b Rightclick\cf0\b0  "Back" button - to glance committed levels and select one to fall back to.\par
\cf3\b Esc\cf0\b0  - to quit/exit/leave. same as closing the window.\par
\cf3\b Tab\cf0\b0  - to change focus between the text/edit boxes (just try it.)\par
\par
these below only work when the \ul big\ulnone  text/edit box is in focus:\par

\pard\li1440\sa200\sl276\slmult1\cf3\b f\cf0\b0  - to find the next match (downward) from the caret position.\par
\cf3\b Shift+f\cf0\b0  - to find the next match (upward) from the caret position.\par
\i (note: "the line" refers to the current line, that's where the caret is on.)\i0\par
\cf3\b Enter\cf0\b0  - to copy the line.\par
\cf3\b Space\cf0\b0  - to page down\par
\cf3\b Shift+Space\cf0\b0  - to page up\par
\cf3\b s\cf0\b0  - to move the caret to the beginning/start of the line.\par
\cf3\b e\cf0\b0  - to move the caret to the end of the line.\par
\cf3\b t\cf0\b0  - to move the caret to the top of the text.\par
\cf3\b b\cf0\b0  - to move the caret to the bottom of the text.\par
\cf3\b + or =\cf0\b0  - to increase font size (same as \cf3\b Ctrl+WheelUp\cf0\b0 ).\par
\cf3\b minus "-"\cf0\b0  - to decrease font size (same as \cf3\b Ctrl+WheelDown\cf0\b0 ).\par
\i (side note: "caret" may be known as "cursor" to some people.)\i0\par

\pard\sa200\sl276\slmult1\par
\tab these below only work when the \ul small\ulnone  text/edit box is in focus:\par

\pard\li1440\sa200\sl276\slmult1\cf3\b Enter\cf0\b0  - to commit a level (which makes the current result becoming the base/source of the next level.) same as clicking the button Go.\par

\pard\sa200\sl276\slmult1\par

\pard\li720\sa200\sl276\slmult1\cf3\b Backtick (``)\cf0\b0  - since users mostly use this tool to locate the specific line of text and copy it for whatever subsequent actions to be taken, which typically involves Tab and then Enter (if the first line is the one wanted), backtick is an alternative which can supersede both keys by just hit backtick twice instead.\par

\pard\sa200\sl276\slmult1\par
The \b improvised shorthand\b0  feature:\par

\pard\li720\sa200\sl276\slmult1 this feature can be triggered through "\cf2\b\\/\cf0\b0 " (a \b backslash\b0  followed by a \b slash\b0 ), a "regex like" syntax style, which toggle it on or off. so, enter "\cf2\b\\/tr\cf0\b0 " will bring you those lines contained "\cf4\i the road\cf0\i0 ", "\cf4\i two roads\cf0\i0 ", as well as "\cf4\i them really\cf0\i0 " out of the poem, whereas enter "\cf2\b oo \\/a?a\cf0\b0 " or "\cf2\b\\/a?a\\/ oo\cf0\b0 " will return you these two lines "\cf4\i And looked down one as far as I could\cf0\i0 " and "\cf4\i Then took the other, as just as fair,\cf0\i0 " in which, the "\cf2\b ?\cf0\b0 " represents any valid character regex recognized it as "word" element. Besides, the space has been cared so that the toggle works across it. for instance, enter "\cf2\b\\/tr yw\cf0\b0 " or "\cf2\b\\/yw tr\cf0\b0 " will show this line "\cf4\i Two roads diverged in a yellow wood,\cf0\i0 " where the space is still a separator as always, even though the shorthand feature is toggled on.\par

\pard\sa200\sl276\slmult1\par
\tab three more elements of the shorthand syntax:\par
\par

\pard\fi-360\li1800\sa200\sl276\slmult1 1, \b extra qualifier\b0  (within a single word);\par
2, \b escape character\b0  (between words);\par
3, a single \b character\b0  representing any "\b non-word\b0 " one (between words).\par

\pard\sa200\sl276\slmult1\par

\pard\li720\sa200\sl276\slmult1 the most basic usage of improvised shorthand is to match the specific "phrase" by just entering the first letter of each word constituted it (for instance, "\cf2\b sameto\cf0\b0 " can match "\cf4\i some are more equal than others\cf0\i0 ") yet sometimes we may want to further narrow down the result. if "\cf2\b slh\cf0\b0 " brings you both "\cf4\i she likes him\cf0\i0 " and "\cf4\i she loves him\cf0\i0 ", an extra qualifier to pinpoint one may be useful. it could simply be a "v" for this example hence "\cf2\b slv/h\cf0\b0 " (in which, the "/" after the "v" tells the system that it's just an extra qualifier to the "l", not for a word starts with "v",) returns you "\cf4\i she loves him\cf0\i0 " only. likewise, you may sometimes want to tell the system exactly what the word should be ending with, for instance, if "\cf2\b ioy\cf0\b0 " brings you both "\cf4\i i owe you\cf0\i0 " and "\cf4\i i own you\cf0\i0 ", "\cf2\b ioe//y\cf0\b0 " (in which, the "//" after the "e" tells the system that it's a word ending character, that is, "oe//" represents a word starts with "o" and ends with "e") returns you "\cf4\i i owe you\cf0\i0 " only. and no matter how rare (and impractical) it may be, you may have multiple of these qualifiers for a word, such as, "\cf2\b cm/c/e//d//\cf0\b0 " can match the word "\cf4\i complicated\cf0\i0 " whereas "\cf2\b td/i/u//s//\cf0\b0 " can match the word "\cf4\i tedious\cf0\i0 ".\par
\par
a \b backslash\b0  "\cf2\b\\\cf0\b0 " may be used to escape a single character after it. its target should be a "word" character because you don't need to escape otherwise. for all "non-word" character, just enter them directly except "\\" itself which requires a \b double backslash\b0  "\cf2\b\\\\\cf0\b0 " for a single one. for regex syntax such as "\\s", it becomes "\\\\s" (not "\\\\\\s" you may presume.) in other words, all escaped character will only be matched barely itself. for \ul consecutive\ulnone  "word" characters that need to be escaped, enclose them in the \cf2\b\\Q\cf0\b0  \cf2\b\\E\cf0\b0  pair, similar idea as in regex. one thing to note though, escaped character may affect the word immediately after it, for instance, "\cf2\b z\\xy\cf0\b0 " may bring you "\cf4\i zebra xylophone\cf0\i0 " rather than "\cf4\i zombie x yesterday\cf0\i0 ".\par
\par
an \b apostrophe\b0  "\cf2\b\f1 '\cf0\b0\f0 " may be used to represent any "non-word" character. that is, same as "\\\\W" (the regex syntax "\\W" for a "non-word" character). it may be useful at the (beginning and/or ending) ends of the "phrase" if it can further narrow down the result or you simply want it to be a part of the match.\par

\pard\sa200\sl276\slmult1\par
The \b matches highlighting\b0  feature:\par

\pard\li720\sa200\sl276\slmult1 the \ul default behavior\ulnone  is to highlight no more than a thousand matches (which can improve the response time when too many matches are found.) it can nonetheless be overridden by a long press of your left mouse button on one of the available gui buttons (i.e. go or refresh or back), whereas long press means press it down and hold on for \ul one second or longer\ulnone . therefore, highlighting will cover the whole thing even if the total number of matches is over \ul one thousand\ulnone . this \ul long-press-to-override\ulnone  is not a toggle, it's just an \ul one-off\ulnone  thing, so do it every time you want more than the default.\par

\pard\sa200\sl276\slmult1\par
End of this help page.\par
}
)
StringReplace, HelpPage, HelpPage, `n, `r`n, All
Haystack=
(
The Road Not Taken
BY ROBERT FROST
Two roads diverged in a yellow wood,
And sorry I could not travel both
And be one traveler, long I stood
And looked down one as far as I could
To where it bent in the undergrowth;
Then took the other, as just as fair,
And having perhaps the better claim,
Because it was grassy and wanted wear;
Though as for that the passing there
Had worn them really about the same,
And both that morning equally lay
In leaves no step had trodden black.
Oh, I kept the first for another day!
Yet knowing how way leads on to way,
I doubted if I should ever come back.
I shall be telling this with a sigh
Somewhere ages and ages hence:
Two roads diverged in a wood, and I—
I took the one less traveled by,
And that has made all the difference.
)
n := 1, pn := n - 1, Haystack%n% := Haystack%pn% := Haystack, NeedleHistory := ""
regFayt := regCase := regWwrap := 1, regWhole := regLinum := fromWordWrap := fromAssortment := 0
theGui:
Gui, tf:New, +HwndHGUI
Gui, +Delimiter`n
Gui, +Resize +MinSize +MaximizeBox
Gui, Font, s10, Arial New
Gui, Add, Text,, O p t i o n s:
begFayt := regFayt ? "Checked" : ""
Gui, Add, Checkbox, ym vFayt %begFayt% Tabstop gtfFaytHdlr, Instant Filter (aka Find As You Type)
begCase := regCase ? "Checked" : ""
Gui, Add, Checkbox, ym vCase %begCase% Tabstop gtfOptsHdlr, Case Insensitive
begWhole := regWhole ? "Checked" : ""
Gui, Add, Checkbox, ym vWhole %begWhole% Tabstop gtfOptsHdlr, Whole Word
begLinum := regLinum ? "Checked" : ""
Gui, Add, Checkbox, ym vLinum %begLinum% Tabstop gtfLineHdlr, Line Number
beg_Wrap := regWwrap ? "Checked" : ""
Gui, Add, Checkbox, ym vWwrap %beg_Wrap% Tabstop gtfWrapHdlr, Word Wrap
Gui, Font, s18, Arial New
Gui, Add, Text, xm Section, Filter:
w := A_ScreenWidth - 330
Gui, Add, ComboBox, w%w% ys vNeedleCB gtfFpatHdlr +hwndhEdit1
Gui, Add, Button, h38 ys Default, Go
Gui, Font, s10, Arial New
Gui, Add, Button, h38 ys, Refresh
Gui, Font, s18, Arial New
Gui, Add, Button, h38 ys, Back
h := A_ScreenHeight - 143, w := A_ScreenWidth - 30
begWwrap := regWwrap ? "+Wrap" : "+HScroll"
Gui, Font, s18, Consolas
Gui, Add, Custom, ClassRICHEDIT50W x10 h%h% w%w% %begWwrap% +VScroll +hwndHRE +0x0004	;ES_MULTILINE
h := A_ScreenHeight - 66, w := A_ScreenWidth - 6
Gui, Show, h%h% w%w%, The Text Filter,, The Text Filter (Version: %Version%) - Level %n%   ( Base %blc% Lines )   %SelectedFile%
RE := GetTomDoc(HRE)
RenewGui:
If regWwrap
	SendMessage, 1101, 1, 0x0044, , % "ahk_id " . HRE	;turn ReadOnly Off w/o HScroll
Else SendMessage, 1101, 1, 0x00C4, , % "ahk_id " . HRE	;turn ReadOnly Off w/ HScroll
SendMessage, 0x0443, 0, 0xFFFFEE, , % "ahk_id " . HRE	;light Up BackgroundColor
If fromAssortment
	fromAssortment := False
Else ControlSetText, RICHEDIT50W1, % Haystack%n%, A
ControlGetText, Hays, RICHEDIT50W1, A
StringReplace, Hays, Hays, `r`n, `n, All	;for RichEdit
StrReplace(Haystack, "`n",, oc), oc += StrLen(Haystack) and SubStr(Haystack, 0) != "`n" ? 1 : 0, lsl := StrLen(oc), indent := (lsl + 2) * 10
RE.Range(0, StrLen(Hays)).SetIndents(-indent, indent, 0)
If fromWordWrap
	sPat := Needle%n%, fromWordWrap := False
Else ControlGetText, sPat, Edit1, A
If StrLen(sPat)
{	Gosub, MakerFnm
	If StrLen(r) and RegExMatch(Hays, r)
	{	Progress, zh0 w300 c10 fs18, `nHang On...`n,, Working On Matches Highlighting, Segoe UI
		mpos := 1, mlen := 0
		While, (mpos := RegExMatch(Hays, r, nm, mpos + mlen)) and (A_Index < 1000 or lbduration > 1000)
			mlen := StrLen(nm), RE.Range(mpos - 1, mpos + mlen - 1).Font.BackColor := 0x00FFFF, npos := mpos
		lbduration := 0
		Progress, Off
	}
}
SendMessage, 0x0443, 0, 0xF0F0F0, , % "ahk_id " . HRE	;tune Down BackgroundColor
If regWwrap
	SendMessage, 1101, 1, 0x0844, , % "ahk_id " . HRE	;turn ReadOnly On w/o HScroll
Else SendMessage, 1101, 1, 0x08C4, , % "ahk_id " . HRE	;turn ReadOnly On w/ HScroll
ControlFocus, Edit1, A
SetWinTitle:
Gosub, tfCount_bl
WinSetTitle, The Text Filter,, The Text Filter (Version: %Version%) - Level %n%   ( Base %blc% Lines )   %SelectedFile%
Return
tfCount_bl:
StrReplace(Haystack%n%, "`n",, blc), blc += StrLen(Haystack%n%) and SubStr(Haystack%n%, 0) != "`n" ? 1 : 0
Return
RClickMenu:
	ControlGetText, Hays, RICHEDIT50W1, A
	MouseGetPos, rbx, rby, mow, moc
	If (mow = HGUI) and rby > 30
		If (moc = "Button8")
			Goto, ShowTrail
		Else If (moc not = "Edit1")
		{	Menu, myMenu, Add
			Menu, myMenu, DeleteAll
			Menu, myMenu, Add, Show Assorted Matches, MenuHandler
			If SubStr(Hays, 1, 19) = "Assorted Matches:`t("
				Menu, myMenu, Add, Put Assorted Matches In TreeView, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, Increase Font Size, MenuHandler
			Menu, myMenu, Add, Decrease Font Size, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, Paste Into The Base, MenuHandler
			Menu, myMenu, Add, Open A File, MenuHandler
			Menu, myMenu, Add, Reload The File, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, List Global Variables, MenuHandler
			If (moc = "RICHEDIT50W1")
			{	Menu, myMenu, Add
				Menu, myMenu, Add, Copy The Line, MenuHandler
				Menu, myMenu, Add, Go To Rightmost Of The Line, MenuHandler
				Menu, myMenu, Add, Go To Leftmost Of The Line, MenuHandler
				Menu, myMenu, Add, Go To The Bottom, MenuHandler
				Menu, myMenu, Add, Go To The Top, MenuHandler
				Menu, myMenu, Add, Page Down, MenuHandler
				Menu, myMenu, Add, Page Up, MenuHandler
			}
			Menu, myMenu, Add
			Menu, myMenu, Add, Downward Find Next Match, MenuHandler
			Menu, myMenu, Add, Upward Find Next Match, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, Override The 1K Default, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, Show/Hide Help Page, MenuHandler
			Menu, myMenu, Show
		}
	Return
	MenuHandler:
		If A_ThisMenuItem = Show Assorted Matches
			Goto, !a
		Else If A_ThisMenuItem = Put Assorted Matches In TreeView
			Goto, AssTreeView
		Else If A_ThisMenuItem = Increase Font Size
			Goto, +
		Else If A_ThisMenuItem = Decrease Font Size
			Goto, -
		Else If A_ThisMenuItem = Paste Into The Base
			Goto, !v
		Else If A_ThisMenuItem = Open A File
			Goto, ^o
		Else If A_ThisMenuItem = Reload The File
			Goto, ^r
		Else If A_ThisMenuItem = List Global Variables
		{	Gui, tf:Destroy
			Haystack := ListGlobalVars(), Haystack1 := Haystack0 := Haystack, Needle1 := "", n := 1, pn := n - 1
			Goto, theGui
		}Else If A_ThisMenuItem = Copy The Line
		{	MouseMove, rbx, rby
			Send, {LButton}
			Goto, ~Enter
		}Else If A_ThisMenuItem = Go To Rightmost Of The Line
		{	MouseMove, rbx, rby
			Send, {LButton}{End}
		}Else If A_ThisMenuItem = Go To Leftmost Of The Line
		{	MouseMove, rbx, rby
			Send, {LButton}{Home}
		}Else If A_ThisMenuItem = Go To The Bottom
		{	MouseMove, rbx, rby
			Send, {LButton}^{End}
		}Else If A_ThisMenuItem = Go To The Top
		{	MouseMove, rbx, rby
			Send, {LButton}^{Home}
		}Else If A_ThisMenuItem = Page Down
		{	MouseMove, rbx, rby
			Send, {LButton}{PgDn}
		}Else If A_ThisMenuItem = Page Up
		{	MouseMove, rbx, rby
			Send, {LButton}{PgUp}
		}Else If A_ThisMenuItem = Downward Find Next Match
		{	ControlFocus, RICHEDIT50W1, A
			Goto, NextMatch
		}Else If A_ThisMenuItem = Upward Find Next Match
		{	ControlFocus, RICHEDIT50W1, A
			Uffm := True	;Upward FNM From Menu
			Goto, NextMatch
		}Else If A_ThisMenuItem = Override The 1K Default
		{	lbduration := 1234
			Goto, RenewGui
		}Else If A_ThisMenuItem = Show/Hide Help Page
			Goto, F1
		Return
ShowTrail:
	Menu, myTrail, Add
	Menu, myTrail, DeleteAll
	While, A_Index < n
	{	tn := n - A_Index, theNeedle := Needle%tn%, bn := n, n := tn
		Gosub, tfCount_bl
		n := bn
		Menu, myTrail, Add, Level &%tn% remains %blc% lines after this filter: %theNeedle%, BtsLevel
	}
	bn := n, n := 0
	Gosub, tfCount_bl
	n := bn
	Menu, myTrail, Add, Origin %blc% lines (Level &0), BtsLevel
	Menu, myTrail, Show
	Return
	BtsLevel:
		n -= A_ThisMenuItemPos
		If n < 1
			Haystack1 := Haystack0, Needle1 := "", n := 1
		pn := n - 1
		ControlSetText, Edit1, % Needle%n%, The Text Filter
		Gosub, RenewGui
		Return
tfFaytHdlr:
	regFayt := Fayt()
	If regFayt
		If (Haystack%n% != Haystack%pn%)
		{	Needle%n% := Fpat()
			theHaystack := Haystack%n%, theNeedle := Needle%n%
			n += 1, pn := n - 1
			Haystack%n% := theHaystack, Needle%n% := theNeedle
			Gosub, SetWinTitle
		}
tfOptsHdlr:
	ControlFocus, Edit1, A
	Goto, tfFpatHdlr
tfLineHdlr:
	If regLinum := Linum()
	{	Progress, zh0 w300 c10 fs18, `nHang On...`n,, Working On Line Numbering, Segoe UI
		StrReplace(Haystack, "`n",, oc), oc += StrLen(Haystack) and SubStr(Haystack, 0) != "`n" ? 1 : 0, lsl := StrLen(oc)
		HaystackN =
		Loop, Parse, Haystack, `n
			HaystackN .= SubStr(StrRepeat(A_Space, lsl) A_Index, 1-lsl) ":" A_Space A_LoopField "`n"
		Progress, Off
		Haystack0 := HaystackN
	}Else Haystack0 := Haystack
	Loop, % n
	{	nn := A_Index, pn := nn - 1
		Haystack%nn% := RegExReplace(Haystack%pn%, Maker(Needle%nn%))
	}
	Goto, RenewGui
tfWrapHdlr:
	fromWordWrap := True
	regWwrap := Wwrap()
	Needle%n% := Fpat()
	Gui, tf:Destroy
	Gosub, theGui
	ControlSetText, Edit1, % Needle%n%, A
	Send, {End}
	Return
tfButtonGo:
	ControlFocus, Edit1, A
	Progress, zh0 w380 c10 fs18, `nCommitting This Level`n,, Prompt, Segoe UI
	CommitLevel = 1
tfFpatHdlr:
	If not regFayt and not CommitLevel
		Return
	Gui, Submit, NoHide
	regCase := Case(), regWhole := Whole()
	Needle%n% := Fpat()
	If not StrLen(Needle%n%) and CommitLevel
	{	CommitLevel = 0
		Goto, tfButtonBack
	}
	theHaystack := RegExReplace(Haystack%pn%, Maker(Needle%n%))
	If CommitLevel
	{	If not StrLen(NeedleHistory)
			NeedleHistory := Needle%n%, Added := 1
		Else If not RegExMatch(NeedleHistory, (regCase ? "i" : "") "m`a)^\Q" Needle%n% "\E$")
			NeedleHistory := Needle%n% "`n" NeedleHistory, Added := 1
		Else Added := 0
		If Added
		{	GuiControl,, NeedleCB, `n%NeedleHistory%
			ControlSetText, Edit1, % Needle%n%
		}
		If (theHaystack = Haystack%pn%)
		{	Progress, Off
			MsgBox, Result No Change Thus Not Anew Level
		}Else
		{	If not regFayt
				Haystack%n% := theHaystack
			Gosub, tfCount_bl
			If (blc < 2) and not (Needle%n% == Needle%pn%)
				Progress, zh0 w450 c10 fs18, `nUltimate/Empty Result Reached`nThus Level Not Advanced`n,, Prompt, Segoe UI
			Else
			{	n += 1, pn := n - 1
				Haystack%n% := theHaystack
				Send, ^a
			}
		}
		CommitLevel = 0
		Sleep, 500
		Progress, Off
	}Else Haystack%n% := theHaystack
	Gosub, RenewGui
	If not regFayt and not StrLen(Haystack%n%)
	{	Progress, zh0 w450 c10 fs18, `nEmpty Result Reached`n`nFalling Back Now`n,, Prompt, Segoe UI
		Sleep, 1000
		Haystack%n% := Haystack%pn%
		ControlSetText, Edit1, % Needle%pn%
		Gosub, RenewGui
		Send, ^a
		Progress, Off
	}
	Return
	Maker(t) {
		t := tw4sh(t), r := (Case() ? "i" : "") "m`a)^(?!"
		Loop, Parse, t, % A_Space
			If SubStr(A_LoopField, 1, 1) = "-"
				r .= "(?!.*(" (Whole() ? "\b" : "") SubStr(A_LoopField, 2) (Whole() ? "\b" : "") "))"
			Else r .= "(?=.*(" (Whole() ? "\b" : "") A_LoopField (Whole() ? "\b" : "") "))"
		r .= ").*\R?"
		Return r
	}
tfButtonRefresh:
	Goto, RenewGui
tfButtonBack:
	ControlFocus, Edit1, A
	Needle%n% := Fpat()
	If (n = 1) and (Haystack1 != Haystack0)
		Haystack1 := Haystack0, Needle1 := ""
	Else If (Haystack%n% = Haystack%pn%) and (Needle%n% = Needle%pn%)
		If n > 2
			n -= 2, pn := n - 1
		Else If n = 2
			n := 1, pn := n - 1, Haystack1 := Haystack0, Needle1 := ""
		Else n = 0
	Else n -= 1, pn := n - 1
	If n
	{	ControlSetText, Edit1, % Needle%n%
		Gosub, RenewGui
		Send, ^a
		If (n = 1) and (Haystack1 = Haystack0)
		{	Progress, zh0 w380 c10 fs18, `nReturned To The Origin`n,, Prompt, Segoe UI
			Sleep, 500
			Progress, Off
		}Else
		{	Progress, zh0 w380 c10 fs18, `nReturned To Level %n%`n,, Prompt, Segoe UI
			Sleep, 500
			Progress, Off
		}
		Return
	}Else Goto, tfGuiClose
tfGuiSize:
	If A_EventInfo = 1
		Return
	GuiControl, Move, RICHEDIT50W1, % "h" (A_GuiHeight - 97) " w" (A_GuiWidth - 14)
	Return
tfGuiEscape:
tfGuiClose:
	IfExist, tfWork.rtf
		FileDelete, tfWork.rtf
	IfExist, tfHelp.rtf
		FileDelete, tfHelp.rtf
	ExitApp

EventHandler(wParam, lParam, msg, hwnd) {
	static lbdowntime := 0
	global lbduration := 0
	If msg = 0x201	;WM_LBUTTONDOWN
		lbdowntime := A_TickCount
	Else If msg = 0x202	;WM_LBUTTONUP
		lbduration := A_TickCount - lbdowntime
	Return
}
GetTomDoc(HRE) {	;written by "@just me"
	Static IID_ITextDocument := "{8CC497C0-A1DF-11CE-8098-00AA0047BE5D}"
	DocObj := 0
	If DllCall("SendMessage", "Ptr", HRE, "UInt", 0x043C, "Ptr", 0, "PtrP", IRichEditOle, "UInt")	;EM_GETOLEINTERFACE
	{	DocObj := ComObject(9, ComObjQuery(IRichEditOle, IID_ITextDocument), 1)	; ITextDocument
		ObjRelease(IRichEditOle)
	}
	Return DocObj
}
Fpat() {
	ControlGetText, Fpat, Edit1
	Return Fpat
}
Fayt() {
	GuiControlGet, Fayt,, Button1
	Return Fayt
}
Case() {
	GuiControlGet, Case,, Button2
	Return Case
}
Whole() {
	GuiControlGet, Whole,, Button3
	Return Whole
}
Linum() {
	GuiControlGet, Linum,, Button4
	Return Linum
}
Wwrap() {
	GuiControlGet, Wwrap,, Button5
	Return Wwrap
}

tw4sh(t) {
	tog := "\/", va := "?\\'\w", ph := "[^" va "\n]", tar := "\" tog ph "*?\K[" va "]", ph := "[^\w\n]"
	If mpos:=RegExMatch(t, tar)	;contained toggle (on)
	{	pb := "[^\w\n]", sh := "((?<=^|" ph ")", st := "(?=(\W|$)))"
		skip_c := skip_w := yb := False
		r := StrReplace(SubStr(t, 1, mpos - 1), tog) sh, rr := xx := ""
		Loop
		{	c := SubStr(t, mpos, 1), cc := SubStr(t, mpos + 1, 1)
			If skip_c
				skip_c--
			Else If SubStr(t, mpos, StrLen(tog)) = tog	;toggle (off) encountered
			{	flush(r, xx, pb, yb, st), rr := xx := ""
				mpos += StrLen(tog), npos := mpos
				If mpos:=RegExMatch(t, tar,, npos)	;found next toggle (on)
					r .= StrReplace(SubStr(t, npos, mpos - npos), tog) sh, rr := xx := ""
				Else
				{	r .= SubStr(t, npos)
					Break
				}
				Continue
			}Else If RegExMatch(c, "\w") or (c = "?")	;valid character
			{	If rr
					If not skip_w and ((cc not = "/") or StrLen(xx))
						flush(r, xx, pb, yb)
					Else If (cc = "/") and not StrLen(xx)
					{	If SubStr(t, mpos + 2, 1) = "/"
							skip_w := skip_w ? skip_w : True, skip_c++
						skip_c++
					}Else{}
				Else If StrLen(rr)
					r .= xx sh, rr := xx := ""
				r .= (c = "?") ? (skip_w ? "\w" : ("\w+?", yb++)) : c (skip_w ? "" : ("\w*?", yb++)), skip_w -= skip_w ? 1 : 0, rr := True
			}Else If (c = A_Space) and rr
				flush(r, xx, pb, yb, st), xx := A_Space, rr := 0
			Else If (c = "'")
				xx .= "\W"
			Else If (c = "\") and skip_w not < 0
				If (cc == "Q")
					skip_c++, skip_w := -1, flush(r, xx, pb, yb)
				Else If (cc == "E")
					skip_c++, skip_w := False
				Else If (cc = c)
					xx .= c
				Else If RegExMatch(cc, "\w")
					skip_c++, xx .= cc
				Else skip_c++, xx .= c cc
			Else xx .= c
			mpos++
			If (mpos > StrLen(t))	;exceeded
			{	If rr
					flush(r, xx, pb, yb, st), rr := xx := ""
				Break
			}
		}
	}Else r := t
	Return r
}
flush(ByRef r, ByRef xx, pb, ByRef yb, st := "") {
	mb := "[^,;\w\n.?!]+?", ss := SubStr(xx, -1), sc := (ss == "\W") or (ss == "\s") or (ss == "\t") or (ss == "\r") or (ss == "\n"), r .= xx ? (RegExMatch(SubStr(xx, 1, 1), "\w") ? mb : pb "*?") xx (st ? ")" : (RegExMatch(ss, "\\?\w$") and not sc ? "" : pb "*?")) : (st ? st : (yb ? (mb, yb--) : "")), xx := ""
}

escrx(h) {
	e := "\().[]*+?{}^$|"

	Loop, Parse, e
		If InStr(h, A_LoopField)
			h := StrReplace(h, A_LoopField, "\" A_LoopField)

	Return h
}

StrRepeat(string, times) {
	Loop, %times%
		output .= string
	Return output
}

FocusedControl() {
	ControlGetFocus, foco, A
	Return foco
}
#If, WinActive("The Text Filter") and FocusedControl() = "Edit1"
	`::
	Tab::ControlFocus, RICHEDIT50W1, A
#If, WinActive("The Text Filter") and FocusedControl() = "RICHEDIT50W1"
	Tab::ControlFocus, Edit1, A
	~Space::PgDn
	~+Space::PgUp
	~s::Home
	~e::End
	~t::^Home
	~b::^End
	-::
		MouseGetPos, mpx, mpy,, moc
		If (moc not = "RICHEDIT50W1")
			MouseMove, 400, 200
		Send, ^{WheelDown}
		If (moc not = "RICHEDIT50W1")
			MouseMove, mpx, mpy
		Return
	+::
	=::
		MouseGetPos, mpx, mpy,, moc
		If (moc not = "RICHEDIT50W1")
			MouseMove, 400, 200
		Send, ^{WheelUp}
		If (moc not = "RICHEDIT50W1")
			MouseMove, mpx, mpy
		Return
	`::
	~Enter::
		ControlGetText, Hays, RICHEDIT50W1, A
		StringReplace, Hays, Hays, `r`n, `n, All	;for RichEdit
		slcs := RE.Selection.Start, slce := RE.Selection.End
		mpos := slce + 1
		While, mpos > 1 and SubStr(Hays, mpos - 1, 1) not = "`n"
			mpos--
		slcs := mpos, mpos := slce + 1, pmax := StrLen(Hays)
		While, mpos <= pmax and not InStr("`r`n", SubStr(Hays, mpos, 1))
			mpos++
		Clipboard := mpos > pmax ? SubStr(Hays, slcs) : SubStr(Hays, slcs, mpos - slcs)
		TakeThis:
		Clipboard := RegExReplace(Clipboard, "^\t?(?: *?\d+?: )?(.*)$", "$1")
		MsgBox,,, Has Put This Line Into Clipboard, 3
		Goto, tfGuiClose
	~f::
	~+f::
		NextMatch:
		ControlGettext, sPat, Edit1, A
		If StrLen(sPat)
			Gosub, MakerFnm
		Else
		{	sPat := RE.Selection.Text
			r := StrLen(sPat) ? (regCase ? "i)" : "") (regWhole ? "\b" : "") escrx(sPat) (regWhole ? "\b" : "") : ""
		}
		If StrLen(r)
		{	ControlGettext, Hays, RICHEDIT50W1, A
			StringReplace, Hays, Hays, `r`n, `n, All	;for RichEdit
			If RegExMatch(Hays, r)
			{	StartHere:
				Gosub, Compute_cp
				If RegExMatch(A_ThisHotkey, "WheelUp|\+f") or Uffm
				{	Gosub, ReverseWay
					mpos -= 1
				}Else mpos := RegExMatch(Hays, r, nm, cp) - 1
				If mpos >= 0
				{	cp := mpos + StrLen(nm)
					RE.Range(mpos, cp).Select
				}Else If (mpos < 0) and not RegExMatch(A_ThisHotkey, "WheelUp|\+f") and not Uffm
				{	RE.Range(0, 0).Select
					Goto, StartHere
				}
			}Else MsgBox, Target Not Found
		}
		Uffm := False	;Upward FNM From Menu
		Return
		MakerFnm:
			sPat := tw4sh(sPat), r := ""
			Loop, Parse, sPat, % A_Space
				If SubStr(A_LoopField, 1, 1) != "-"
					r .= "(" (regWhole ? "\b" : "") A_LoopField (regWhole ? "\b" : "") ")|"
			If StrLen(r)
			{	If SubStr(r, 0) = "|"
					r := SubStr(r, 1, StrLen(r) - 1)
				r := (regCase ? "i" : "") "m`a)" r
			}
			Return
		Compute_cp:
			slcs := RE.Selection.Start, slce := RE.Selection.End, slcs++, slce++
			cp := RegExMatch(A_ThisHotkey, "WheelUp|\+f") or Uffm ? slcs : slce
			Return
		ReverseWay:
			If cp > 1
			{	mpos := 0, pmax := cp - 1
				Loop
				{	npos := RegExMatch(Hays, r, pm, mpos+1)
					If npos between 1 and %pmax%
						mpos := npos, nm := pm
					Else Break
				}
				If (npos > pmax) and mpos > 0
					Return
			}
			mpos := cp - 1, pmax := StrLen(Hays)
			Loop
			{	npos := RegExMatch(Hays, r, pm, mpos+1)
				If npos between %cp% and %pmax%
					mpos := npos, nm := pm
				Else Break
			}
			Return
#If, WinActive("TreeView Of Assorted Matches") and FocusedControl() = "SysTreeView321"
	`::
	~Enter::Gosub, ButtonEnter
#IfWinActive, The Text Filter
	F1::
		ControlGetText, Hays, RICHEDIT50W1, A
		If SubStr(Hays, 1, 17) = "H e l p _ P a g e"
			IfExist, tfWork.rtf
				RE.Open("tfWork.rtf", 0x01, 0)
			Else ControlSetText, RICHEDIT50W1, %B4Help%, A
		Else
		{	B4Help := Hays
			IfExist, tfWork.rtf
				RE.Save(0, 0, 0)
			Else RE.Save("tfWork.rtf", 0x01, 0)
			IfExist, tfHelp.rtf
				FileDelete, tfHelp.rtf
			FileAppend, %HelpPage%, tfHelp.rtf
			RE.Open("tfHelp.rtf", 0x01, 0)
		}
		Return
	~Wheelup::
	~Wheeldown::
		MouseGetPos,,,, moc
		If not StrLen(moc) or not InStr("Edit1|RICHEDIT50W1", moc)
		{	ControlFocus, RICHEDIT50W1, A
			Goto, NextMatch
		}Else Return
	!a::
		fromAssortment := True
		ControlGetText, Hays, RICHEDIT50W1, A
		If SubStr(Hays, 1, 19) = "Assorted Matches:`t("
		{	If SubStr(Hays, 20, 12) = "Alphabetical"
				ControlSetText, RICHEDIT50W1, %AssHays%, A
			Else
				ControlSetText, RICHEDIT50W1, %AAssHays%, A
			Gosub, RenewGui
			Return
		}
		If not StrLen(Haystack%n%)
			Return
		ControlGetText, sPat, Edit1, A
		If not StrLen(sPat)
			Return
		Gosub, MakerFnm
		If not StrLen(r)
			Return
		Progress, zh0 w300 c10 fs18, `nHang On...`n,, Working On Assortment, Segoe UI
		rn = `r`n
		UniqMatches := [], Seq := 0
		Loop, Parse, Haystack%n%, `n
		{	mpos = 1
			worknm:
			npos := RegExMatch(A_LoopField, r, nm, mpos)
			If npos
			{	If UniqMatches.HasKey(nm)
					Ums := UniqMatches[nm], AssHays%Ums% .= InStr(AssHays%Ums%, A_LoopField) ? "" : rn "`t" A_LoopField
				Else
					Seq++, UniqMatches[nm] := Seq, AssHays%Seq% := nm rn "`t" A_LoopField
				mpos := npos + StrLen(nm)
				Goto, worknm
			}
		}
		If Seq
		{	UniqHays := gAAssHays := "", umNoSort := []
			For k, v in UniqMatches
				UniqHays .= rn StrRepeat(A_Space, 3) A_Index ".`t" k, gAAssHays .= rn A_Index ". " AssHays%v%, umNoSort[v] ? umNoSort[v].Insert(k) : umNoSort[v] := k
			AAssHays := "Assorted Matches:`t(Alphabetical Order)" UniqHays rn StrRepeat("-", 50) gAAssHays
			UniqHays := gAssHays := ""
			For k, v in umNoSort
				UniqHays .= rn StrRepeat(A_Space, 3) k ".`t" v, gAssHays .= rn A_Index ". " AssHays%k%
			AssHays := "Assorted Matches:`t(Chronological Order)" UniqHays rn StrRepeat("-", 50) gAssHays
			StringReplace, AssHays, AssHays, `r`n, `n, All	;for RichEdit
			StringReplace, AAssHays, AAssHays, `r`n, `n, All	;for RichEdit
			ControlSetText, RICHEDIT50W1, %AssHays%, A
			Gosub, RenewGui
		}
		Progress, Off
		Return
	AssTreeView:
		ControlGetText, Hays, RICHEDIT50W1, A
		parwin := WinExist("A")
		Gui, tvass:New, +Resize +MinSize +MaximizeBox +Owner%parwin%
		Gui, Font, s18, Consolas
		Gui, Add, Text,, % SubStr(Hays, 20, 12) = "Alphabetical" ? "Assorted Matches: (Alphabetical Order)" : "Assorted Matches: (Chronological Order)"
		;Gui, Add, Button, Hidden Default, Enter
		Gui, Add, TreeView, AltSubmit Section xm w545 gassTV
		Seq := 0, aHays := StrReplace(SubStr(Hays, 20, 12) = "Alphabetical" ? gAAssHays : gAssHays, "`r`n", "`n")
		Loop, Parse, aHays, `n
			If StrLen(A_LoopField)
				If RegExMatch(A_LoopField, "^ *\d+\.[ \t]")
					Seq++, P%Seq% := TV_Add(A_LoopField), Num := 0
				Else Num++, P%Seq%C%Num% := TV_Add(A_LoopField, P%Seq%)
		Gui, Show, x97 y539 h283, TreeView Of Assorted Matches
		GuiControl, Focus, assTV
		Return
	tvassGuiSize:
		If A_EventInfo = 1
			Return
		GuiControl, Move, SysTreeView321, % "h" (A_GuiHeight - 76) " w" (A_GuiWidth - 46)
		Return
	assTV:
		If InStr("DoubleClick|S|K|Normal", A_GuiEvent)
			TV_GetText(theOne, TV_GetSelection())
		If A_GuiEvent = DoubleClick
			Goto, ButtonEnter
		Return
	ButtonEnter:
		StringReplace, theOne, theOne, `r, , All
		Clipboard := theOne
		Goto, TakeThis
		Return
	!v::	;paste Clipboard content into the Haystack
		If StrLen(Clipboard)
		{	Gui, tf:Destroy
			Haystack1 := Haystack0 := Haystack := Clipboard, Needle1 := "", n := 1, pn := n - 1
			Goto, theGui
		}
		Return
	^o::	;open file
		SelectedFile =
		FileSelectFile, SelectedFile
	^r::	;reload file
		If SelectedFile
		{	Gui, tf:Destroy
			FileRead, Haystack, % SelectedFile
			Haystack1 := Haystack0 := Haystack, Needle1 := "", n := 1, pn := n - 1
			Goto, theGui
		}Else Return
	~RButton::Gosub, RClickMenu
#IfWinActive

ListGlobalVars() {	;written by Lexikos
	static hwndEdit, pSFW, pSW, bkpSFW, bkpSW

	If !hwndEdit
	{	dhw := A_DetectHiddenWindows
		DetectHiddenWindows, On
		Process, Exist
		ControlGet, hwndEdit, Hwnd,, Edit1, ahk_class AutoHotkey ahk_pid %ErrorLevel%
		DetectHiddenWindows, %dhw%

		astr := A_IsUnicode ? "astr":"str"
		ptr := A_PtrSize=8 ? "ptr":"uint"
		hmod := DllCall("GetModuleHandle", "str", "user32.dll")
		pSFW := DllCall("GetProcAddress", ptr, hmod, astr, "SetForegroundWindow")
		pSW := DllCall("GetProcAddress", ptr, hmod, astr, "ShowWindow")
		DllCall("VirtualProtect", ptr, pSFW, ptr, 8, "uint", 0x40, "uint*", 0)
		DllCall("VirtualProtect", ptr, pSW, ptr, 8, "uint", 0x40, "uint*", 0)
		bkpSFW := NumGet(pSFW+0, 0, "int64")
		bkpSW := NumGet(pSW+0, 0, "int64")
	}

	If (A_PtrSize=8)
	{	NumPut(0x0000C300000001B8, pSFW+0, 0, "int64")	;return TRUE
		NumPut(0x0000C300000001B8, pSW+0, 0, "int64")	;return TRUE
	}Else
	{	NumPut(0x0004C200000001B8, pSFW+0, 0, "int64")	;return TRUE
		NumPut(0x0008C200000001B8, pSW+0, 0, "int64")	;return TRUE
	}

	ListVars

	NumPut(bkpSFW, pSFW+0, 0, "int64")
	NumPut(bkpSW, pSW+0, 0, "int64")

	ControlGetText, text,, ahk_id %hwndEdit%

	RegExMatch(text, "sm)(?<=^Global Variables \(alphabetical\)`r`n-{50}`r`n).*", text)
	Return text
}
Last edited by SundayProgrammer on 05 May 2021, 00:12, edited 4 times in total.
ozzii
Posts: 481
Joined: 30 Oct 2013, 06:04

Re: Text Filter

13 Apr 2021, 03:40

:thumbup:
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

18 Apr 2021, 12:20

the script is updated. → viewtopic.php?p=393548#p393548 ← click this link
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

21 Apr 2021, 09:57

^([[:blank:]]*\d+:[[:space:]])?[[:blank:]]*(#if\w*((,?[[:blank:]]*|[[:blank:]]+).+)?$|\K.+?::|\K[a-z]\w*:|\K\w+\(.*?\)[[:blank:]]*\{)

👆instant index page👆which contains just #if, hotkeys/hotstrings/labels, and functions. i found it quite useful in navigation within the code (typically when it consists so many lines), and as a quick overview of the whole.

👇it looks like this👇
Spoiler

please, if you have other usage examples, share. thanks.
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

24 Apr 2021, 23:20

the script is updated. → viewtopic.php?p=393548#p393548 ← click this link

planet-seven-g added an overall rightclick menu which should have covered all the features available.
ozzii
Posts: 481
Joined: 30 Oct 2013, 06:04

Re: Text Filter

25 Apr 2021, 06:30

SundayProgrammer wrote:
24 Apr 2021, 23:20
added an overall rightclick menu which should have covered all the features available.
:bravo: :dance:
User avatar
Kellyzkorner_NJ
Posts: 84
Joined: 20 Oct 2017, 18:33

Re: Text Filter

28 Apr 2021, 12:13

I have a question for the newest version of this wonderful script, I used it on my really large main AHK script (55000 + lines) and since the results were very long, I'm needing to scroll down to see the next page. On every wheeldown I get the message 'Target Not Found', which I know is by design but is there a way to turn that off for the situations where you just want to scroll through results? I don't want to comment out the whole msgbox so I don't lose the functionality altogether. Thanks very much and thank you for your time on working on this. It is making and will make a lot of people happy I'm sure!
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

28 Apr 2021, 15:22

Kellyzkorner_NJ wrote:
28 Apr 2021, 12:13
I have a question for the newest version of this wonderful script, I used it on my really large main AHK script (55000 + lines) and since the results were very long, I'm needing to scroll down to see the next page. On every wheeldown I get the message 'Target Not Found', which I know is by design but is there a way to turn that off for the situations where you just want to scroll through results? I don't want to comment out the whole msgbox so I don't lose the functionality altogether. Thanks very much and thank you for your time on working on this. It is making and will make a lot of people happy I'm sure!
hi @Kellyzkorner_NJ

it seems to me that you triggered the "wheel search mode" by "accident". the trigger of the "wheel search mode" all depends on where your mouse pointer is currently at when you roll your mouse wheel. if it resides within the "main" box (the richedit control), it won't trigger the "wheel search mode". otherwise, it will "find next match" in your mouse wheel rolling direction, that's the behavior of the "wheel search mode".

the "wheel search mode" was devised as a quick way to reveal the exact location of each match. if it doesn't make sense to you (or you found it confusing more than convenient), just disable it as shown below.
see illustration
in fact, i seldom use it since the "matches highlighting" was implemented. it was kept because (a) it doesn't bother me to leave it there aside; (b) i still use "search by selection" which shares the same piece of code; (c) i can use it just in case.

besides, i think removing the "target not found" msgbox is absolutely fine as one may not need to be prompted explicitly imo. you may remove it as shown below.
see illustration
the "wheel search mode" is not a new feature, it's been there since "planet-two".

thank you for letting me know the issues you faced, i need it for improvement and also for a better design in the future.

thanks.
User avatar
Kellyzkorner_NJ
Posts: 84
Joined: 20 Oct 2017, 18:33

Re: Text Filter

28 Apr 2021, 16:27

@SundayProgrammer Thank you for the clarification and examples, I’ll do that. I LOVE the yellow highlighting and the right click. You’re very welcome. Have a great rest of your day!
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

05 May 2021, 00:17

the script is updated. → viewtopic.php?p=393548#p393548 ← click this link

planet-seven-h added a treeview gui for "assorted matches".
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

01 Jun 2021, 02:29

star-zero

this new version is an overhaul of the coding. basically no new features, not yet. i've packed almost everything into a class object. and hopefully, it's much more convenient to put it into other script now, if one may want to do so.

its entrance is the start method, which uses just one parameter. four kinds are defined: (1) "load clipboard"; or (2) "load file thefilename"; or (3) a text string, on itself becomes the haystack; or (4) not to supply a parameter, which will pick up whatever left off in the object's memory.

besides, multiple instances coexisting are possible now. thus each instance/window will have its own unique id. and be shown on the window title ribbon. this id is equivalent to the object variable name. examples: as in (i) tf.start() and (ii) w1 := new tf, w1.start("hello world"), where tf (of "i") and w1 (of "ii") are the so-called id respectively.

the hotkeys part can all be removed (if one may want to do so) without problem, except two labels (subroutines), the pastehaysub and the openfilesub, which are hardcoded in the class to be accessing to. and also please keep the rbutton hotkey, so that, some of the features can have at least a way to be accessed when all keyboard-wise hotkeys are gone.

2021-6-2: star-zero-a made some adjustments.

2021-6-4: star-zero-b found a bug, and fixed.

2021-6-7: star-zero-c made three tweaks:

(a) added the "copy selection" ability to the existing feature "copy the line" (thus it becomes "copy the line/selection" now.) so, if some text is selected in the big text/edit box (the "richedit50w1"), it will be copied instead of the entire line. selection or selected text refers to the result of mouse lbutton drag or keyboard shift+arrows (and the likes) or mix-of-both shift+click operation. please note a subtle difference between "copy selection" and "copy the line" when it was accessed through rightclick menu. that is, the former doesn't care where the mouse pointer was at when the rbutton was clicked. coz selection was made, just copy it, that's all. whereas the later assumed the mouse pointer was placed on the line to be copied though, when the rbutton was clicked.

(b) added certain checking for the "find selection" (an associated feature within the "find next match") so that the small text/edit box (the "edit1") need not be cleared beforehand. kinda prioritized "selection" over "edit1" for the "find next match" operation. however, this change applies only to the wheel search mode and those through rightclick menu, whereas the keyboard hotkey "f" and "+f" remained same as before.

(c) right click now caters drag operation that may be used in other scripts. as a result, the rightclick menu will not be shown if it's a rbutton drag operation.
....................
that's it for now.

Code: Select all

SetWorkingDir %A_ScriptDir%
RE_Dll := DllCall("LoadLibrary", "Str", "Msftedit.dll", "Ptr")
thePoem=
(
The Road Not Taken
BY ROBERT FROST
Two roads diverged in a yellow wood,
And sorry I could not travel both
And be one traveler, long I stood
And looked down one as far as I could
To where it bent in the undergrowth;
Then took the other, as just as fair,
And having perhaps the better claim,
Because it was grassy and wanted wear;
Though as for that the passing there
Had worn them really about the same,
And both that morning equally lay
In leaves no step had trodden black.
Oh, I kept the first for another day!
Yet knowing how way leads on to way,
I doubted if I should ever come back.
I shall be telling this with a sigh
Somewhere ages and ages hence:
Two roads diverged in a wood, and I—
I took the one less traveled by,
And that has made all the difference.
)
; tf.start("Load Clipboard")
; msgbox
; tf.start("Load File log.txt")
; msgbox
tf.start(thePoem)
; msgbox
; tf.start()	;from what's leftoff in the object's memory

;multiple instances can be coexisting as below
; w1 := new tf, w1.start("Load Clipboard")
; w2 := new tf, w2.start("Load File log.txt")
; w3 := new tf, w3.start(thePoem)
; msgbox
; w3.start()	;from what's leftoff in the object's memory
Return

FocusedControl() {
	ControlGetFocus, foco, A
	Return foco
}
#If, WinActive("The Text Filter") and FocusedControl() = "Edit1"
	`::
	Tab::ControlFocus, RICHEDIT50W1, A
#If, WinActive("The Text Filter") and FocusedControl() = "RICHEDIT50W1"
	Tab::ControlFocus, Edit1, A
	Space::PgDn
	+Space::PgUp
	s::Home
	e::End
	t::^Home
	b::^End
	-::%_cwi_%.DecrFontSize()
	+::
	=::%_cwi_%.IncrFontSize()
	`::
	Enter::%_cwi_%.TakeThisLine()
	f::%_cwi_%.FindNextMatch()
	+f::%_cwi_%.FindNextMatch(1)
#If, WinActive("TreeView Of Assorted Matches") and FocusedControl() = "SysTreeView321"
	`::
	Enter::%_cwi_%.tvButtonEnter()
#IfWinActive, The Text Filter
	F1::%_cwi_%.ToggleHelpPage(_cwi_)
	~WheelUp::
	~WheelDown::%_cwi_%.WheelSearchMode()
	!a::%_cwi_%.ShowAssMatches()
	^t::%_cwi_%.AssTreeView(_cwi_)
	!v::Gosub, PasteHaySub
	^o::Gosub, OpenFileSub
	^r::%_cwi_%.ReloadFile()
	~RButton::%_cwi_%.RClickMenu()
#IfWinActive

PasteHaySub:
	If _uii_ := %_cwi_%.PasteIntoHaystack()
		%_uii_% := new tf, %_uii_%.start("Load Clipboard")
	Return

OpenFileSub:
	If _OpFi_ := %_cwi_%.OpenFile()
	{	RegExMatch(_OpFi_, "^\w+(?=\t)", _uii_), RegExMatch(_OpFi_, "\t\K.+$", _usf_)
		%_uii_% := new tf, %_uii_%.start("Load File " _usf_)
	}
	Return

WM_ACTIVATE(wParam) {
	global _cwi_
	If (wParam > 0)
		If WinActive("The Text Filter") or WinActive("TreeView Of Assorted Matches")
		{	WinGetTitle, t, A
			If RegExMatch(t, "\| *?(\w*?) *?\|", m) and StrLen(m1)
				_cwi_ := m1
			Else _cwi_ := "Oops..."
		}Else _cwi_ := "Other Windows"
}
EventHandler(wParam, lParam, msg, hwnd) {
	static lbdowntime := 0
	global lbduration := 0
	If msg = 0x201	;WM_LBUTTONDOWN
		lbdowntime := A_TickCount
	Else If msg = 0x202	;WM_LBUTTONUP
		lbduration := A_TickCount - lbdowntime
	Return
}

Class tf {
	static o := {}
	v := {}

	start(Param := "") {
		OnMessage(0x6, "WM_ACTIVATE")
		OnMessage(0x201, "EventHandler")	;WM_LBUTTONDOWN
		OnMessage(0x202, "EventHandler")	;WM_LBUTTONUP
		this.id := this.GetObjVarNam(), v := this.ov := (this.id = this.__class ? "o" : "v")
		this[v].Version := "Star-Zero-C"
		If StrLen(Param)
		{	this[v].SelectedFile := ""
			If Param = Load Clipboard
				this[v].Haystack := Clipboard
			Else If SubStr(Param, 1, 9) = "Load File"
				this[v].Haystack := this.FileRead(SubStr(Param, 11))
			Else this[v].Haystack := Param
			this[v].Haystack := StrReplace(this[v].Haystack, "`r`n", "`n")

			this[v].Fayt := this[v].Case := this[v].Wwrap := True, this[v].Whole := this[v].Linum := False, this[v].n := 1, this[v].Hs[0] := this[v].GetAddress("Haystack"), this[v].Hs[1] := this[v].Haystack
		}
		this.BuildGui(this.id), WM_ACTIVATE(1), this.CleanUp(this.id)
	}
	FileRead(theFile) {
		FileRead, theHays, % this[this.ov].SelectedFile := theFile
		Return theHays
	}
	HsP(n) {
		If (n > 1)
			Return this[this.ov].Hs[n - 1]
		Else Return StrGet(this[this.ov].Hs[0])
	}
	NeeP(n) {
		If (n > 1)
			Return this[this.ov].Needle[n - 1]
		Else Return ""
	}
	GetObjVarNam() {
		r := "(\w+?):.+?\{address: " Format("0x{:X}", &this) "\}"
		Return RegExMatch(this.ListGlobalVars(), r, m) ? m1 : ""
	}

BuildGui(id) {
	global NeedleCB
	static Fayt, Case, Whole, Linum, Wwrap
	v := this.ov

	Gui, %id%:New, +Labeltf.Gui_On
	Gui, +Delimiter`n
	Gui, +Resize +MinSize +MaximizeBox
	Gui, Font, s10, Arial New
	Gui, Add, Text,, O p t i o n s:

	begFayt := this[v].Fayt ? "Checked" : ""
	Gui, Add, Checkbox, ym vFayt %begFayt% Tabstop, Instant Filter (aka Find As You Type)
	OnSuch := this.Fayt_OnToggle.Bind(this)
	GuiControl +g, Button1, % OnSuch

	begCase := this[v].Case ? "Checked" : ""
	Gui, Add, Checkbox, ym vCase %begCase% Tabstop, Case Insensitive
	OnSuch := this.Case_OnToggle.Bind(this)
	GuiControl +g, Button2, % OnSuch

	begWhole := this[v].Whole ? "Checked" : ""
	Gui, Add, Checkbox, ym vWhole %begWhole% Tabstop, Whole Word
	OnSuch := this.Whole_OnToggle.Bind(this)
	GuiControl +g, Button3, % OnSuch

	begLinum := this[v].Linum ? "Checked" : ""
	Gui, Add, Checkbox, ym vLinum %begLinum% Tabstop, Line Number
	OnSuch := this.Linum_OnToggle.Bind(this)
	GuiControl +g, Button4, % OnSuch

	beg_Wrap := this[v].Wwrap ? "Checked" : ""
	Gui, Add, Checkbox, ym vWwrap %beg_Wrap% Tabstop, Word Wrap
	OnSuch := this.Wwrap_OnToggle.Bind(this)
	GuiControl +g, Button5, % OnSuch

	Gui, Font, s18, Arial New
	Gui, Add, Text, xm Section, Filter:

	w := A_ScreenWidth - 330
	Gui, Add, ComboBox, w%w% ys vNeedleCB +hwndhEdit1
	OnSuch := this.Fpat_OnTyping.Bind(this)
	GuiControl +g, %hEdit1%, % OnSuch

	Gui, Add, Button, h38 ys Default, Go
	OnSuch := this.Go_OnClick.Bind(this)
	GuiControl +g, Button6, % OnSuch

	Gui, Font, s10, Arial New
	Gui, Add, Button, h38 ys, Refresh
	OnSuch := this.Refresh_OnClick.Bind(this)
	GuiControl +g, Button7, % OnSuch

	Gui, Font, s18, Arial New
	Gui, Add, Button, h38 ys, Back
	OnSuch := this.Back_OnClick.Bind(this)
	GuiControl +g, Button8, % OnSuch

	h := A_ScreenHeight - 143, w := A_ScreenWidth - 30, begWwrap := this[v].Wwrap ? "+Wrap" : "+HScroll"
	Gui, Font, s18, Consolas
	Gui, Add, Custom, ClassRICHEDIT50W x10 h%h% w%w% %begWwrap% +VScroll +hwndHRE +0x804	;ES_READONLY = 0x800, ES_MULTILINE = 0x4

	h := A_ScreenHeight - 66, w := A_ScreenWidth - 6
	Gui, Show, h%h% w%w%, The Text Filter

	this.HRE := HRE, this.RE := this.GetTomDoc(HRE)
	this.RenewGui()	;show content
}

Gui_OnSize() {
	If A_EventInfo = 1
		Return
	GuiControl, Move, RICHEDIT50W1, % "h" (A_GuiHeight - 97) " w" (A_GuiWidth - 14)
}

Gui_OnEscape() {
	Gui, %A_Gui%:Destroy
	IfExist, tf_%A_Gui%Work.rtf
		FileDelete, tf_%A_Gui%Work.rtf
	IfExist, tfHelp.rtf
		FileDelete, tfHelp.rtf
}

Gui_OnClose() {
	Gui, %A_Gui%:Destroy
	IfExist, tf_%A_Gui%Work.rtf
		FileDelete, tf_%A_Gui%Work.rtf
	IfExist, tfHelp.rtf
		FileDelete, tfHelp.rtf
}

CleanUp(id) {
	IfExist, tf_%id%Work.rtf
		FileDelete, tf_%id%Work.rtf
	IfExist, tfHelp.rtf
		FileDelete, tfHelp.rtf
}

GetTomDoc(HRE) {	;written by "@just me"
	Static IID_ITextDocument := "{8CC497C0-A1DF-11CE-8098-00AA0047BE5D}"
	DocObj := 0
	If DllCall("SendMessage", "Ptr", HRE, "UInt", 0x043C, "Ptr", 0, "PtrP", IRichEditOle, "UInt")	;EM_GETOLEINTERFACE
	{	DocObj := ComObject(9, ComObjQuery(IRichEditOle, IID_ITextDocument), 1)	; ITextDocument
		ObjRelease(IRichEditOle)
	}
	Return DocObj
}

Fayt_OnToggle() {
	v := this.ov, n := this[v].n
	If not (this[v].Fayt := this.Fayt())
		If (this[v].Hs[n] != this.HsP(n))
		{	this[v].Needle[n] := this.Fpat(), theHaystack := this[v].Hs[n]
			n := (this[v].n += 1), this[v].Hs[n] := theHaystack
			this.UpdateTitle()
		}
	ControlFocus, Edit1, A
	this.Fpat_OnTyping()
}

Case_OnToggle() {
	this[this.ov].Case := this.Case()
	ControlFocus, Edit1, A
	this.Fpat_OnTyping()
}

Whole_OnToggle() {
	this[this.ov].Whole := this.Whole()
	ControlFocus, Edit1, A
	this.Fpat_OnTyping()
}

Linum_OnToggle() {
	v := this.ov
	If this[v].Linum := this.Linum()
	{	Progress, zh0 w300 c10 fs18, `nHang On...`n,, Working On Line Numbering, Segoe UI
		lsl := StrLen(this.LineCount())
		HsN =
		Loop, Parse, % this[v].Haystack, `n`r
			HsN .= SubStr(this.StrRepeat(A_Space, lsl) A_Index, 1 - lsl) ":" A_Space A_LoopField "`n"
		Progress, Off
		this[v].HaystackN := HsN, this[v].Hs[0] := this[v].GetAddress("HaystackN")
	}Else this[v].Hs[0] := this[v].GetAddress("Haystack")
	Loop, % this[v].n
		this[v].Hs[A_Index] := RegExReplace(this.HsP(A_Index), this.Maker(this[v].Needle[A_Index]))
	this.RenewGui(this.Fpat())
}

Wwrap_OnToggle() {
	v := this.ov, this[v].Wwrap := this.Wwrap(), n := this[v].n, this[v].Needle[n] := this.Fpat()
	this.BuildGui(this.id)
;	this.RenewGui(this[v].Needle[n])
	GuiControl,, NeedleCB, % "`n" this[v].NeedleHistory
	ControlSetText, Edit1, % this[v].Needle[n], A
	this.RenewGui(this[v].Needle[n])
	Send, {End}
}

Fpat_OnTyping() {
	v := this.ov
	If not this[v].Fayt and not this[v].CommitLevel
		Return
	Gui, Submit, NoHide
	n := this[v].n, this[v].Needle[n] := theNeedle := this.Fpat()
	If not StrLen(theNeedle) and this[v].CommitLevel
	{	this[v].CommitLevel := False, this.Back_OnClick()
		Return
	}
	theHaystack := RegExReplace(this.HsP(n), this.Maker(theNeedle))
	If this[v].CommitLevel
	{	If not StrLen(this[v].NeedleHistory)
			this[v].NeedleHistory := theNeedle, Added := True
		Else If not RegExMatch(this[v].NeedleHistory, (this[v].Case ? "i" : "") "m`a)^\Q" theNeedle "\E$")
			this[v].NeedleHistory := theNeedle "`n" this[v].NeedleHistory, Added := True
		Else Added := False
		If Added
		{	GuiControl,, NeedleCB, % "`n" this[v].NeedleHistory
			ControlSetText, Edit1, % theNeedle
		}
		If (theHaystack = this.HsP(n))
			Progress, zh0 w450 c10 fs18, `nResult No Change Thus Not Anew Level`n,, Prompt, Segoe UI
		Else
		{	If not this[v].Fayt
				this[v].Hs[n] := theHaystack
			If (this.LineCount(n) < 2) and not (theNeedle == this.NeeP(n))
				Progress, zh0 w450 c10 fs18, `nUltimate/Empty Result Reached`nThus Level Not Advanced`n,, Prompt, Segoe UI
			Else
			{	n := (this[v].n += 1), this[v].Hs[n] := theHaystack
				Send, ^a
			}
		}
		this[v].CommitLevel := False
		Sleep, 500
		Progress, Off
	}Else this[v].Hs[n] := theHaystack
	this.RenewGui(theNeedle)
	If not this[v].Fayt and not StrLen(this[v].Hs[n])
	{	Progress, zh0 w450 c10 fs18, `nEmpty Result Reached`n`nFalling Back Now`n,, Prompt, Segoe UI
		Sleep, 1000
		this[v].Hs[n] := this.HsP(n)
		ControlSetText, Edit1, % this.NeeP(n)
		this.RenewGui(this.Fpat())
		Send, ^a
		Progress, Off
	}
}
Maker(t) {
	v := this.ov, t := this.tw4sh(t), r := (this[v].Case ? "i" : "") "m`a)^(?!"
	Loop, Parse, t, % A_Space
		If SubStr(A_LoopField, 1, 1) = "-"
			r .= "(?!.*(" (this[v].Whole ? "\b" : "") SubStr(A_LoopField, 2) (this[v].Whole ? "\b" : "") "))"
		Else r .= "(?=.*(" (this[v].Whole ? "\b" : "") A_LoopField (this[v].Whole ? "\b" : "") "))"
	r .= ").*\R?"
	Return r
}

Go_OnClick() {
	ControlFocus, Edit1, A
	Progress, zh0 w380 c10 fs18, `nCommitting This Level`n,, Prompt, Segoe UI
	this[this.ov].CommitLevel := True, this.Fpat_OnTyping()
}

Refresh_OnClick() {
	this.RenewGui(this[this.ov].Needle[this[this.ov].n] := this.Fpat(), "retain")
}

Back_OnClick() {
	ControlFocus, Edit1, A
	v := this.ov, n := this[v].n, this[v].Needle[n] := this.Fpat()
	If (n = 1) and (this[v].Hs[n] != this.HsP(n))
		this[v].Hs[n] := this.HsP(n), this[v].Needle[n] := ""
	Else If (this[v].Hs[n] = this.HsP(n)) and (this[v].Needle[n] = this.NeeP(n))
		If (n > 2)
			n := (this[v].n -= 2)
		Else If (n = 2)
			n := this[v].n := 1, this[v].Hs[n] := this.HsP(n), this[v].Needle[n] := ""
		Else n := this[v].n := 0
	Else n := (this[v].n -= 1)
	If n
	{	ControlSetText, Edit1, % this[v].Needle[n]
		this.RenewGui(this[v].Needle[n])
		Send, ^a
		If (n = 1) and (this[v].Hs[n] = this.HsP(n))
		{	Progress, zh0 w380 c10 fs18, `nReturned To The Origin`n,, Prompt, Segoe UI
			Sleep, 500
			Progress, Off
		}Else
		{	Progress, zh0 w380 c10 fs18, `nReturned To Level %n%`n,, Prompt, Segoe UI
			Sleep, 500
			Progress, Off
		}
	}Else this.Gui_OnClose()
}

Fpat() {
	GuiControlGet, Fpat,, Edit1
	If not StrLen(Fpat)
		ControlGetText, Fpat, Edit1
	Return Fpat
}
Fayt() {
	GuiControlGet, Fayt,, Button1
	If not StrLen(Fayt)
		ControlGetText, Fayt, Button1
	Return Fayt
}
Case() {
	GuiControlGet, Case,, Button2
	If not StrLen(Case)
		ControlGetText, Case, Button2
	Return Case
}
Whole() {
	GuiControlGet, Whole,, Button3
	If not StrLen(Whole)
		ControlGetText, Whole, Button3
	Return Whole
}
Linum() {
	GuiControlGet, Linum,, Button4
	If not StrLen(Linum)
		ControlGetText, Linum, Button4
	Return Linum
}
Wwrap() {
	GuiControlGet, Wwrap,, Button5
	If not StrLen(Wwrap)
		ControlGetText, Wwrap, Button5
	Return Wwrap
}

RenewGui(theNee := "", AMt := "") {
	global lbduration
	v := this.ov, n := this[v].n
	If this.Fpat() not = theNee
		Goto, WorkInterrupt
	this.ToggleReadOnly(this.HRE, this[v].Wwrap)
	If (AMt = "retain")
		theHays := this.RE.Range(0, 999999999).Text
	Else If StrLen(AMt)
		theHays := this[v][AMt]
	Else theHays := this[v].Hs[n]
	ControlSetText, RICHEDIT50W1, %theHays%, A
	indent := (StrLen(this.LineCount()) + 2) * 10, this.RE.Range(0, StrLen(theHays)).SetIndents(-indent, indent, 0)
	If this.Fpat() not = theNee
		Goto, WIP_Interrupt
	If StrLen(theNee) and StrLen(r := this.MakerC(theNee)) and RegExMatch(theHays, r)	;RegExReplace(theHays, r,, mc) and (not this[v].Fayt or AMt = "retain" or mc < StrLen(theNee) * 100)
	{	Progress, zh0 w300 c10 fs18, `nHang On...`n,, Working On Matches Highlighting, Segoe UI
 		mpos := 1, mlen := 0
 		While, (mpos := RegExMatch(theHays, r, nm, mpos + mlen)) and (A_Index < 1000 or lbduration > 1000)
 			mlen := StrLen(nm), this.RE.Range(mpos - 1, mpos + mlen - 1).Font.BackColor := 0x00FFFF, npos := mpos
 		lbduration := 0
 		Progress, Off
	}
	WIP_Interrupt:
	this.ToggleReadOnly(this.HRE, this[v].Wwrap)
	ControlFocus, Edit1, A
	this.UpdateTitle()
	WorkInterrupt:
	If this.Fpat() not = theNee
		this.Fpat_OnTyping()
}

UpdateTitle() {
	WinSetTitle, The Text Filter, % ("", v := this.ov, Version := this[v].Version, id := this.id, n := this[v].n, lc := this.LineCount(n), SelectedFile := this[v].SelectedFile), The Text Filter (Version: %Version%)  |  %id%  |  Level %n%  |  Line Count: %lc%  |  %SelectedFile%
}

LineCount(n := 0) {
	If n is digit
		theHays := this.HsP(n + 1)
	Else theHays := n
	StrReplace(theHays, "`n",, lc), lc += StrLen(theHays) and SubStr(theHays, 0) != "`n" ? 1 : 0
	Return lc
}

ToggleReadOnly(HRE, Wwrap) {
	ControlGet, Style, Style,,, ahk_id %HRE%
	If not (Style & 0x800)
		SendMessage, 0x443, 0, 0xF0F0F0,, ahk_id %HRE%	;tune Down BackgroundColor
     SendMessage, 1101, 1, (Style & 0x800) ? (Wwrap ? 0x44 : 0xC4) : (Wwrap ? 0x844 : 0x8C4),, ahk_id %HRE%	;ES_READONLY = 0x800
	If (Style & 0x800)
		SendMessage, 0x443, 0, 0xFFFFEE,, ahk_id %HRE%	;light Up BackgroundColor
}

MakerC(theNee) {
	theNee := this.tw4sh(theNee), r := "", v := this.ov
	Loop, Parse, theNee, % A_Space
		If SubStr(A_LoopField, 1, 1) != "-"
			r .= "(" (this[v].Whole ? "\b" : "") A_LoopField (this[v].Whole ? "\b" : "") ")|"
	If StrLen(r)
	{	If SubStr(r, 0) = "|"
			r := SubStr(r, 1, StrLen(r) - 1)
		r := (this[v].Case ? "i" : "") "m`a)" r
	}
	Return r
}

IncrFontSize() {
	MouseGetPos, mpx, mpy,, moc
	If (moc not = "RICHEDIT50W1")
		MouseMove, 400, 200
	Send, ^{WheelUp}
	If (moc not = "RICHEDIT50W1")
		MouseMove, mpx, mpy
}

DecrFontSize() {
	MouseGetPos, mpx, mpy,, moc
	If (moc not = "RICHEDIT50W1")
		MouseMove, 400, 200
	Send, ^{WheelDown}
	If (moc not = "RICHEDIT50W1")
		MouseMove, mpx, mpy
}

TakeThisLine() {
	slcs := this.RE.Selection.Start, slce := this.RE.Selection.End, Hays := this.RE.Range(0, 999999999).Text
	If (slce > slcs)
	{	Clipboard := SubStr(Hays, slcs + 1, slce - slcs)
		Progress, zh0 w380 c10 fs18, `nSelection Copied`n,, Prompt, Segoe UI
		Sleep, 500
		Progress, Off
		Return
	}
	mpos := slce + 1
	While, mpos > 1 and not InStr("`r`n", SubStr(Hays, mpos - 1, 1))
		mpos--
	slcs := mpos, mpos := slce + 1, pmax := StrLen(Hays)
	While, mpos <= pmax and not InStr("`r`n", SubStr(Hays, mpos, 1))
		mpos++
	this.FinalTouch(mpos > pmax ? SubStr(Hays, slcs) : SubStr(Hays, slcs, mpos - slcs))
}

FinalTouch(theOne) {
	Clipboard := RegExReplace(theOne, "^\t?(?: *?\d+?: )?(.*)$", "$1")
	MsgBox,,, Has Put This Line Into Clipboard, 3
}

FindNextMatch(Reverse := 0) {
	If StrLen(sPat := this.Fpat())
		r := this.MakerC(sPat)
	Else r := "nothing to be matched this"
	slcs := this.RE.Selection.Start, slce := this.RE.Selection.End
	If (slce > slcs) and (RegExMatch(A_ThisHotkey, "^\+?f$") and StrLen(sPat) ? False : not RegExMatch(this.RE.Selection.Text, r))
		r := this.MakerS(this.RE.Selection.Text)
	If StrLen(r) and RegExMatch(Hays := this.RE.Range(0, 999999999).Text, r)
	{	StartHere:
		slcs := this.RE.Selection.Start, slce := this.RE.Selection.End, slcs++, slce++
		cp := Reverse ? slcs : slce
		If Reverse
		{	If cp > 1
			{	mpos := 0, pmax := cp - 1
				Loop
				{	npos := RegExMatch(Hays, r, pm, mpos + 1)
					If npos between 1 and %pmax%
						mpos := npos, nm := pm
					Else Break
				}
				found := (npos > pmax) and mpos > 0
			}
			If not found
			{	mpos := cp - 1, pmax := StrLen(Hays)
				Loop
				{	npos := RegExMatch(Hays, r, pm, mpos + 1)
					If npos between %cp% and %pmax%
						mpos := npos, nm := pm
					Else Break
				}
			}
		}Else mpos := RegExMatch(Hays, r, nm, cp)
		mpos--
		If mpos >= 0
		{	cp := mpos + StrLen(nm)
			this.RE.Range(mpos, cp).Select
		}Else If (mpos < 0) and not Reverse
		{	this.RE.Range(0, 0).Select
			Goto, StartHere
		}
	}
}
MakerS(st) {
	Return (StrLen(st), v := this.ov) ? (this[v].Case ? "i)" : "") (this[v].Whole ? "\b" : "") this.escrx(st) (this[v].Whole ? "\b" : "") : ""
}

WheelSearchMode() {
	MouseGetPos,,,, moc
	If not StrLen(moc) or not InStr("Edit1|RICHEDIT50W1", moc)
	{	ControlFocus, RICHEDIT50W1, A
		this.FindNextMatch(InStr(A_ThisHotkey, "WheelUp"))
	}
}

ShowAssMatches() {
	If this.RE.Range(0, 19).Text = "Assorted Matches:`t("
	{	sPat := this.Fpat()
		If this.RE.Range(19, 31).Text = "Alphabetical"
			this.RenewGui(sPat, "AssHays")
		Else this.RenewGui(sPat, "AAssHays")
		Return
	}
	z := this.ov, n := this[z].n
	If not StrLen(this[z].Hs[n]) or not StrLen(sPat := this.Fpat()) or not StrLen(r := this.MakerC(sPat))
		Return
	Progress, zh0 w300 c10 fs18, `nHang On...`n,, Working On Assortment, Segoe UI
	UniqMatches := [], Seq := 0
	Loop, Parse, % this[z].Hs[n], `n`r
	{	mpos = 1
		worknm:
		npos := RegExMatch(A_LoopField, r, nm, mpos)
		If npos
		{	If UniqMatches.HasKey(nm)
				Ums := UniqMatches[nm], AssHays%Ums% .= InStr(AssHays%Ums%, A_LoopField) ? "" : "`n`t" A_LoopField
			Else
				Seq++, UniqMatches[nm] := Seq, AssHays%Seq% := nm "`n`t" A_LoopField
			mpos := npos + StrLen(nm)
			Goto, worknm
		}
	}
	If Seq
	{	UniqHays := tvAAssHays := "", umNoSort := []
		For k, v in UniqMatches
			UniqHays .= "`n" this.StrRepeat(A_Space, 3) A_Index ".`t" k, tvAAssHays .= "`n" A_Index ". " AssHays%v%, umNoSort[v] ? umNoSort[v].Insert(k) : umNoSort[v] := k
		AAssHays := "Assorted Matches:`t(Alphabetical Order)" UniqHays "`n" this.StrRepeat("-", 50) tvAAssHays
		UniqHays := tvAssHays := ""
		For k, v in umNoSort
			UniqHays .= "`n" this.StrRepeat(A_Space, 3) k ".`t" v, tvAssHays .= "`n" A_Index ". " AssHays%k%
		AssHays := "Assorted Matches:`t(Chronological Order)" UniqHays "`n" this.StrRepeat("-", 50) tvAssHays
		this[z].AssHays := AssHays, this[z].AAssHays := AAssHays, this[z].tvAssHays := tvAssHays, this[z].tvAAssHays := tvAAssHays
		this.RenewGui(sPat, "AssHays")
	}
	Progress, Off
}

AssTreeView(id) {
	If this.RE.Range(0, 19).Text not = "Assorted Matches:`t("
		Return
	parwin := WinExist("A")
	Gui, tvass:New, +Owner%parwin%
	Gui, +Resize +MinSize +MaximizeBox
	Gui, +Labeltf.tvGui_On
	Gui, Font, s18, Consolas
	Gui, Add, Text,, % this.RE.Range(19, 31).Text = "Alphabetical" ? "Assorted Matches: (Alphabetical Order)" : "Assorted Matches: (Chronological Order)"
	;Gui, Add, Button, Hidden Default, Enter
	Gui, Add, TreeView, AltSubmit Section xm w545
	OnSuch := this.tvEventHdlr.Bind(this)
	GuiControl +g, SysTreeView321, % OnSuch
	Seq := 0, aHays := this.RE.Range(19, 31).Text = "Alphabetical" ? this[this.ov].tvAAssHays : this[this.ov].tvAssHays, lc := this.LineCount(aHays) - 1
	Loop, Parse, aHays, `n`r
		If StrLen(A_LoopField)
			If RegExMatch(A_LoopField, "^ *\d+\.[ \t]")
				Seq++, P%Seq% := TV_Add(A_LoopField), Num := 0
			Else Num++, P%Seq%C%Num% := TV_Add(A_LoopField, P%Seq%)
	Gui, Show, x97 y539 h283, TreeView Of Assorted Matches  |  %id%  |  Line Count: %lc%
	GuiControl, Focus, SysTreeView321
}

tvGui_OnSize() {
	If A_EventInfo = 1
		Return
	GuiControl, Move, SysTreeView321, % "h" (A_GuiHeight - 76) " w" (A_GuiWidth - 46)
}

tvGui_OnEscape() {
	Gui, %A_Gui%:Destroy
}

tvGui_OnClose() {
	Gui, %A_Gui%:Destroy
}

tvEventHdlr() {
	If InStr("DoubleClick|S|K|Normal", A_GuiEvent)
		TV_GetText(theOne, TV_GetSelection())
	this[this.ov].theOne := theOne
	If A_GuiEvent = DoubleClick
		this.tvButtonEnter()
}

tvButtonEnter() {
	this.FinalTouch(this[this.ov].theOne)
}

PasteIntoHaystack() {
	global _cwi_
	If StrLen(Clipboard)
	{	InputBox, id, Object Instance Identification, No space is allowed and please keep it short. Accept the given ID means not to create a new window but replace the content of the current one.,,,,,,,, % _cwi_
		If not ErrorLevel and StrLen(id)
		{	If RegExMatch(id, "\W")
				id := RegExReplace(id, "\W")
			If (id = _cwi_)
				this.start("Load Clipboard")
			Else Return id
		}
	}Else MsgBox, Clipboard is empty
}

OpenFile() {
	global _cwi_
	FileSelectFile, SelectedFile
	If StrLen(SelectedFile)
	{	InputBox, id, Object Instance Identification, No space is allowed and please keep it short. Accept the given ID means not to create a new window but replace the content of the current one.,,,,,,,, % _cwi_
		If not ErrorLevel and StrLen(id)
		{	If RegExMatch(id, "\W")
				id := RegExReplace(id, "\W")
			If (id = _cwi_)
				this.start("Load File " SelectedFile), this[this.ov].SelectedFile := SelectedFile
			Else Return id "`t" SelectedFile
		}
	}
}

ReloadFile() {
	If StrLen(SelectedFile := this[this.ov].SelectedFile)
		this.start("Load File " SelectedFile)
}

RClickMenu() {
	global lbduration
	MouseGetPos, xb4, yb4
	While, GetKeyState("RButton", "P")
		Continue
	MouseGetPos, rbx, rby, mow, moc
	If Abs(rbx - xb4) > 10 or Abs(rby - yb4) > 10
		Return
	If (mow = (HGUI := WinExist("A"))) and rby > 30
		If (moc = "Button8")
			this.ShowTrail()
		Else If (moc not = "Edit1")
		{	Menu, myMenu, Add
			Menu, myMenu, DeleteAll
			Menu, myMenu, Add, Show Assorted Matches, MenuHandler
			If this.RE.Range(0, 19).Text = "Assorted Matches:`t("
				Menu, myMenu, Add, Put Assorted Matches In TreeView, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, Increase Font Size, MenuHandler
			Menu, myMenu, Add, Decrease Font Size, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, Paste Into The Base, MenuHandler
			Menu, myMenu, Add, Open A File, MenuHandler
			Menu, myMenu, Add, Reload The File, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, List Global Variables, MenuHandler
			If (moc = "RICHEDIT50W1")
			{	Menu, myMenu, Add
				Menu, myMenu, Add, Copy The Line/Selection, MenuHandler
				Menu, myMenu, Add, Go To Rightmost Of The Line, MenuHandler
				Menu, myMenu, Add, Go To Leftmost Of The Line, MenuHandler
				Menu, myMenu, Add, Go To The Bottom, MenuHandler
				Menu, myMenu, Add, Go To The Top, MenuHandler
				Menu, myMenu, Add, Page Down, MenuHandler
				Menu, myMenu, Add, Page Up, MenuHandler
			}
			Menu, myMenu, Add
			Menu, myMenu, Add, Downward Find Next Match, MenuHandler
			Menu, myMenu, Add, Upward Find Next Match, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, Override The 1K Default, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, Show/Hide Help Page, MenuHandler
			Menu, myMenu, Show
		}
	Return
	MenuHandler:
		WinWaitActive, ahk_id %HGUI%
		If A_ThisMenuItem = Show Assorted Matches
			this.ShowAssMatches()
		Else If A_ThisMenuItem = Put Assorted Matches In TreeView
			this.AssTreeView(this.id)
		Else If A_ThisMenuItem = Increase Font Size
			this.IncrFontSize()
		Else If A_ThisMenuItem = Decrease Font Size
			this.DecrFontSize()
		Else If A_ThisMenuItem = Paste Into The Base
			Gosub, PasteHaySub
		Else If A_ThisMenuItem = Open A File
			Gosub, OpenFileSub
		Else If A_ThisMenuItem = Reload The File
			this.ReloadFile()
		Else If A_ThisMenuItem = List Global Variables
			this.start(this.ListGlobalVars())
		Else If A_ThisMenuItem = Copy The Line/Selection
		{	If this.RE.Selection.End = this.RE.Selection.Start
				MouseClick, Left, rbx, rby
			this.TakeThisLine()
		}Else If A_ThisMenuItem = Go To Rightmost Of The Line
		{	MouseClick, Left, rbx, rby
			Send, {End}
		}Else If A_ThisMenuItem = Go To Leftmost Of The Line
		{	MouseClick, Left, rbx, rby
			Send, {Home}
		}Else If A_ThisMenuItem = Go To The Bottom
		{	MouseClick, Left, rbx, rby
			Send, ^{End}
		}Else If A_ThisMenuItem = Go To The Top
		{	MouseClick, Left, rbx, rby
			Send, ^{Home}
		}Else If A_ThisMenuItem = Page Down
		{	MouseClick, Left, rbx, rby
			Send, {PgDn}
		}Else If A_ThisMenuItem = Page Up
		{	MouseClick, Left, rbx, rby
			Send, {PgUp}
		}Else If A_ThisMenuItem = Downward Find Next Match
		{	ControlFocus, RICHEDIT50W1, A
			this.FindNextMatch()
		}Else If A_ThisMenuItem = Upward Find Next Match
		{	ControlFocus, RICHEDIT50W1, A
			this.FindNextMatch(1)
		}Else If A_ThisMenuItem = Override The 1K Default
			lbduration := 1234, this.RenewGui(this.Fpat(), "retain")
		Else If A_ThisMenuItem = Show/Hide Help Page
			this.ToggleHelpPage(this.id)
		Return
}

ShowTrail() {
	HGUI := WinExist("A"), v := this.ov, n := this[v].n
	Menu, myTrail, Add
	Menu, myTrail, DeleteAll
	While, A_Index < n
	{	tn := n - A_Index, theNeedle := this[v].Needle[tn], lc := this.LineCount(tn)
		Menu, myTrail, Add, Level &%tn% remains %lc% lines after this filter: %theNeedle%, BtsLevel
	}
	lc := this.LineCount()
	Menu, myTrail, Add, Origin %lc% lines (Level &0), BtsLevel
	Menu, myTrail, Show
	Return
	BtsLevel:
		WinWaitActive, ahk_id %HGUI%
		n -= A_ThisMenuItemPos
		If n < 1
			n := this[v].n := 1, this[v].Hs[n] := this.HsP(n), this[v].Needle[n] := ""
		Else this[v].n := n
		ControlSetText, Edit1, % this[v].Needle[n]
		this.RenewGui(this[v].Needle[n])
		Return
}

ToggleHelpPage(id) {
	theFile = tf_%id%Work.rtf
	If this.RE.Range(0, 17).Text = "H e l p _ P a g e"
		IfExist, %theFile%
			this.RE.Open(theFile, 0x01, 0)
		Else this.RenewGui(this.Fpat())
	Else
	{	IfExist, %theFile%
			this.RE.Save(0, 0, 0)
		Else this.RE.Save(theFile, 0x01, 0)
		IfExist, tfHelp.rtf
			FileDelete, tfHelp.rtf
		FileAppend, % this.HelpPage(), tfHelp.rtf
		this.RE.Open("tfHelp.rtf", 0x01, 0)
	}
}

tw4sh(t) {
	tog := "\/", va := "?\\'\w", ph := "[^" va "\n]", tar := "\" tog ph "*?\K[" va "]", ph := "[^\w\n]"
	If mpos:=RegExMatch(t, tar)	;contained toggle (on)
	{	pb := "[^\w\n]", sh := "((?<=^|" ph ")", st := "(?=(\W|$)))"
		skip_c := skip_w := yb := False
		r := StrReplace(SubStr(t, 1, mpos - 1), tog) sh, rr := xx := ""
		Loop
		{	c := SubStr(t, mpos, 1), cc := SubStr(t, mpos + 1, 1)
			If skip_c
				skip_c--
			Else If SubStr(t, mpos, StrLen(tog)) = tog	;toggle (off) encountered
			{	this.flush(r, xx, pb, yb, st), rr := xx := ""
				mpos += StrLen(tog), npos := mpos
				If mpos:=RegExMatch(t, tar,, npos)	;found next toggle (on)
					r .= StrReplace(SubStr(t, npos, mpos - npos), tog) sh, rr := xx := ""
				Else
				{	r .= SubStr(t, npos)
					Break
				}
				Continue
			}Else If RegExMatch(c, "\w") or (c = "?")	;valid character
			{	If rr
					If not skip_w and ((cc not = "/") or StrLen(xx))
						this.flush(r, xx, pb, yb)
					Else If (cc = "/") and not StrLen(xx)
					{	If SubStr(t, mpos + 2, 1) = "/"
							skip_w := skip_w ? skip_w : True, skip_c++
						skip_c++
					}Else{}
				Else If StrLen(rr)
					r .= xx sh, rr := xx := ""
				r .= (c = "?") ? (skip_w ? "\w" : ("\w+?", yb++)) : c (skip_w ? "" : ("\w*?", yb++)), skip_w -= skip_w ? 1 : 0, rr := True
			}Else If (c = A_Space) and rr
				this.flush(r, xx, pb, yb, st), xx := A_Space, rr := 0
			Else If (c = "'")
				xx .= "\W"
			Else If (c = "\") and skip_w not < 0
				If (cc == "Q")
					skip_c++, skip_w := -1, this.flush(r, xx, pb, yb)
				Else If (cc == "E")
					skip_c++, skip_w := False
				Else If (cc = c)
					xx .= c
				Else If RegExMatch(cc, "\w")
					skip_c++, xx .= cc
				Else skip_c++, xx .= c cc
			Else xx .= c
			mpos++
			If (mpos > StrLen(t))	;exceeded
			{	If rr
					this.flush(r, xx, pb, yb, st), rr := xx := ""
				Break
			}
		}
	}Else r := t
	Return r
}

flush(ByRef r, ByRef xx, pb, ByRef yb, st := "") {
	mb := "[^,;\w\n.?!]+?", ss := SubStr(xx, -1), sc := (ss == "\W") or (ss == "\s") or (ss == "\t") or (ss == "\r") or (ss == "\n"), r .= xx ? (RegExMatch(SubStr(xx, 1, 1), "\w") ? mb : pb "*?") xx (st ? ")" : (RegExMatch(ss, "\\?\w$") and not sc ? "" : pb "*?")) : (st ? st : (yb ? (mb, yb--) : "")), xx := ""
}

StrRepeat(string, times) {
	Loop, %times%
		output .= string
	Return output
}
escrx(h) {
	e := "\().[]*+?{}^$|"

	Loop, Parse, e
		If InStr(h, A_LoopField)
			h := StrReplace(h, A_LoopField, "\" A_LoopField)

	Return h
}
ListGlobalVars() {	;written by Lexikos
	static hwndEdit, pSFW, pSW, bkpSFW, bkpSW

	If !hwndEdit
	{	dhw := A_DetectHiddenWindows
		DetectHiddenWindows, On
		Process, Exist
		ControlGet, hwndEdit, Hwnd,, Edit1, ahk_class AutoHotkey ahk_pid %ErrorLevel%
		DetectHiddenWindows, %dhw%

		astr := A_IsUnicode ? "astr":"str"
		ptr := A_PtrSize=8 ? "ptr":"uint"
		hmod := DllCall("GetModuleHandle", "str", "user32.dll")
		pSFW := DllCall("GetProcAddress", ptr, hmod, astr, "SetForegroundWindow")
		pSW := DllCall("GetProcAddress", ptr, hmod, astr, "ShowWindow")
		DllCall("VirtualProtect", ptr, pSFW, ptr, 8, "uint", 0x40, "uint*", 0)
		DllCall("VirtualProtect", ptr, pSW, ptr, 8, "uint", 0x40, "uint*", 0)
		bkpSFW := NumGet(pSFW+0, 0, "int64")
		bkpSW := NumGet(pSW+0, 0, "int64")
	}

	If (A_PtrSize=8)
	{	NumPut(0x0000C300000001B8, pSFW+0, 0, "int64")	;return TRUE
		NumPut(0x0000C300000001B8, pSW+0, 0, "int64")	;return TRUE
	}Else
	{	NumPut(0x0004C200000001B8, pSFW+0, 0, "int64")	;return TRUE
		NumPut(0x0008C200000001B8, pSW+0, 0, "int64")	;return TRUE
	}

	ListVars

	NumPut(bkpSFW, pSFW+0, 0, "int64")
	NumPut(bkpSW, pSW+0, 0, "int64")

	ControlGetText, text,, ahk_id %hwndEdit%

	RegExMatch(text, "sm)(?<=^Global Variables \(alphabetical\)`r`n-{50}`r`n).*", text)
	Return text
}
HelpPage() {
HelpPage=
(
{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fnil\fcharset0 Arial;}{\f1\fnil\fcharset0 Consolas;}}
{\colortbl ;\red255\green0\blue0;\red155\green0\blue211;\red0\green77\blue187;\red0\green176\blue80;\red0\green0\blue255;}
{\*\generator Riched20 10.0.19041}\viewkind4\uc1 
\pard\sa200\sl276\slmult1\b\fs36\lang9 H e l p _ P a g e\b0\par
\par
\cf1\b F1\cf0\b0  - to toggle this help page\par
\par
\b Basic syntax by example:\b0\par

\pard\li720\sa200\sl276\slmult1 coexisting words/strings/patterns -unwanted\par
for instance, \cf2\b\f1 d.*d -the and -oo \\ws\cf0\b0\f0\par
(the 5 segments (space separated) above can go all at once, and yet, they can also be entered one by one. that's the so-called multilevel approach, instead.)\par

\pard\sa200\sl276\slmult1\par
\b Feature keys:\b0\par

\pard\li720\sa200\sl276\slmult1\cf3\b Alt+a\cf0\b0  - to show assorted matches and toggle between chronological and alphabetical order.\par
\cf3\b Ctrl+t\cf0\b0  - to put assorted matches into a treeview gui which may ease the exploration when the list is long and involved multiple groups.\par
\cf3\b WheelDown\cf0\b0  - to find the next match (downward) from the caret position (given that, the mouse pointer is currently outside both text/edit boxes.)\par
\cf3\b WheelUp\cf0\b0  - to find the next match (upward) from the caret position (given that, the mouse pointer is currently outside both text/edit boxes.)\par
\cf3\b Alt+v\cf0\b0  - to paste clipboard content into the big text/edit box.\par
\cf3\b Ctrl+o\cf0\b0  - to open a file.\par
\cf3\b Ctrl+r\cf0\b0  - to reload the file.\par
\cf3\b Rightclick\cf0\b0  "Back" button - to glance committed levels and select one to fall back to.\par
\cf3\b Esc\cf0\b0  - to quit/exit/leave. same as closing the window.\par
\cf3\b Tab\cf0\b0  - to change focus between the text/edit boxes (just try it.)\par
\par
these below only work when the \ul big\ulnone  text/edit box is in focus:\par

\pard\li1440\sa200\sl276\slmult1\cf3\b f\cf0\b0  - to find the next match (downward) from the caret position.\par
\cf3\b Shift+f\cf0\b0  - to find the next match (upward) from the caret position.\par
\i (note: "the line" refers to the current line, that's where the caret is on.)\i0\par
\cf3\b Enter\cf0\b0  - to copy the line.\par
\cf3\b Space\cf0\b0  - to page down\par
\cf3\b Shift+Space\cf0\b0  - to page up\par
\cf3\b s\cf0\b0  - to move the caret to the beginning/start of the line.\par
\cf3\b e\cf0\b0  - to move the caret to the end of the line.\par
\cf3\b t\cf0\b0  - to move the caret to the top of the text.\par
\cf3\b b\cf0\b0  - to move the caret to the bottom of the text.\par
\cf3\b + or =\cf0\b0  - to increase font size (same as \cf3\b Ctrl+WheelUp\cf0\b0 ).\par
\cf3\b minus "-"\cf0\b0  - to decrease font size (same as \cf3\b Ctrl+WheelDown\cf0\b0 ).\par
\i (side note: "caret" may be known as "cursor" to some people.)\i0\par

\pard\sa200\sl276\slmult1\par
\tab these below only work when the \ul small\ulnone  text/edit box is in focus:\par

\pard\li1440\sa200\sl276\slmult1\cf3\b Enter\cf0\b0  - to commit a level (which makes the current result becoming the base/source of the next level.) same as clicking the button Go.\par

\pard\sa200\sl276\slmult1\par

\pard\li720\sa200\sl276\slmult1\cf3\b Backtick (``)\cf0\b0  - since users mostly use this tool to locate the specific line of text and copy it for whatever subsequent actions to be taken, which typically involves Tab and then Enter (if the first line is the one wanted), backtick is an alternative which can supersede both keys by just hit backtick twice instead.\par

\pard\sa200\sl276\slmult1\par
The \b improvised shorthand\b0  feature:\par

\pard\li720\sa200\sl276\slmult1 this feature can be triggered through "\cf2\b\\/\cf0\b0 " (a \b backslash\b0  followed by a \b slash\b0 ), a "regex like" syntax style, which toggle it on or off. so, enter "\cf2\b\\/tr\cf0\b0 " will bring you those lines contained "\cf4\i the road\cf0\i0 ", "\cf4\i two roads\cf0\i0 ", as well as "\cf4\i them really\cf0\i0 " out of the poem, whereas enter "\cf2\b oo \\/a?a\cf0\b0 " or "\cf2\b\\/a?a\\/ oo\cf0\b0 " will return you these two lines "\cf4\i And looked down one as far as I could\cf0\i0 " and "\cf4\i Then took the other, as just as fair,\cf0\i0 " in which, the "\cf2\b ?\cf0\b0 " represents any valid character regex recognized it as "word" element. Besides, the space has been cared so that the toggle works across it. for instance, enter "\cf2\b\\/tr yw\cf0\b0 " or "\cf2\b\\/yw tr\cf0\b0 " will show this line "\cf4\i Two roads diverged in a yellow wood,\cf0\i0 " where the space is still a separator as always, even though the shorthand feature is toggled on.\par

\pard\sa200\sl276\slmult1\par
\tab three more elements of the shorthand syntax:\par
\par

\pard\fi-360\li1800\sa200\sl276\slmult1 1, \b extra qualifier\b0  (within a single word);\par
2, \b escape character\b0  (between words);\par
3, a single \b character\b0  representing any "\b non-word\b0 " one (between words).\par

\pard\sa200\sl276\slmult1\par

\pard\li720\sa200\sl276\slmult1 the most basic usage of improvised shorthand is to match the specific "phrase" by just entering the first letter of each word constituted it (for instance, "\cf2\b sameto\cf0\b0 " can match "\cf4\i some are more equal than others\cf0\i0 ") yet sometimes we may want to further narrow down the result. if "\cf2\b slh\cf0\b0 " brings you both "\cf4\i she likes him\cf0\i0 " and "\cf4\i she loves him\cf0\i0 ", an extra qualifier to pinpoint one may be useful. it could simply be a "v" for this example hence "\cf2\b slv/h\cf0\b0 " (in which, the "/" after the "v" tells the system that it's just an extra qualifier to the "l", not for a word starts with "v",) returns you "\cf4\i she loves him\cf0\i0 " only. likewise, you may sometimes want to tell the system exactly what the word should be ending with, for instance, if "\cf2\b ioy\cf0\b0 " brings you both "\cf4\i i owe you\cf0\i0 " and "\cf4\i i own you\cf0\i0 ", "\cf2\b ioe//y\cf0\b0 " (in which, the "//" after the "e" tells the system that it's a word ending character, that is, "oe//" represents a word starts with "o" and ends with "e") returns you "\cf4\i i owe you\cf0\i0 " only. and no matter how rare (and impractical) it may be, you may have multiple of these qualifiers for a word, such as, "\cf2\b cm/c/e//d//\cf0\b0 " can match the word "\cf4\i complicated\cf0\i0 " whereas "\cf2\b td/i/u//s//\cf0\b0 " can match the word "\cf4\i tedious\cf0\i0 ".\par
\par
a \b backslash\b0  "\cf2\b\\\cf0\b0 " may be used to escape a single character after it. its target should be a "word" character because you don't need to escape otherwise. for all "non-word" character, just enter them directly except "\\" itself which requires a \b double backslash\b0  "\cf2\b\\\\\cf0\b0 " for a single one. for regex syntax such as "\\s", it becomes "\\\\s" (not "\\\\\\s" you may presume.) in other words, all escaped character will only be matched barely itself. for \ul consecutive\ulnone  "word" characters that need to be escaped, enclose them in the \cf2\b\\Q\cf0\b0  \cf2\b\\E\cf0\b0  pair, similar idea as in regex. one thing to note though, escaped character may affect the word immediately after it, for instance, "\cf2\b z\\xy\cf0\b0 " may bring you "\cf4\i zebra xylophone\cf0\i0 " rather than "\cf4\i zombie x yesterday\cf0\i0 ".\par
\par
an \b apostrophe\b0  "\cf2\b\f1 '\cf0\b0\f0 " may be used to represent any "non-word" character. that is, same as "\\\\W" (the regex syntax "\\W" for a "non-word" character). it may be useful at the (beginning and/or ending) ends of the "phrase" if it can further narrow down the result or you simply want it to be a part of the match.\par

\pard\sa200\sl276\slmult1\par
The \b matches highlighting\b0  feature:\par

\pard\li720\sa200\sl276\slmult1 the \ul default behavior\ulnone  is to highlight no more than a thousand matches (which can improve the response time when too many matches are found.) it can nonetheless be overridden by a long press of your left mouse button on one of the available gui buttons (i.e. go or refresh or back), whereas long press means press it down and hold on for \ul one second or longer\ulnone . therefore, highlighting will cover the whole thing even if the total number of matches is over \ul one thousand\ulnone . this \ul long-press-to-override\ulnone  is not a toggle, it's just an \ul one-off\ulnone  thing, so do it every time you want more than the default.\par

\pard\sa200\sl276\slmult1\par
End of this help page.\par
}
)
	Return HelpPage
}
}	;end of class
Last edited by SundayProgrammer on 07 Jun 2021, 03:38, edited 2 times in total.
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

02 Jun 2021, 16:13

the script is updated. → viewtopic.php?p=402780#p402780 ← click this link

star-zero-a made some adjustments.
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

04 Jun 2021, 00:55

the script is updated. → viewtopic.php?p=402780#p402780 ← click this link

star-zero-b found a bug, and fixed.

|
|
today, i want to share a way to overview the object properties and methods out of the script, and also as an example of how "assorted matches" can help.

(this|%?\w+%?)(\.\w+|\[[^[\]]+\])+(\(([^()]*)?\))?|^[[:blank:]]*\K\w+(\(([^()]*)?\))

noted that, the regex isn't 100% accurate. it catched slightly more than supposed, but it's good enough for the job with just 5% (for the time being) unwanted matches.
|
here is the result
|
cheers.
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

07 Jun 2021, 03:46

the script is updated. → viewtopic.php?p=402780#p402780 ← click this link

star-zero-c made three tweaks.
User avatar
Kellyzkorner_NJ
Posts: 84
Joined: 20 Oct 2017, 18:33

Re: Text Filter

07 Jun 2021, 12:12

Hello, Just to let you know, I'm trying the most recent C version, I'm getting an, Error: The following variable name contains an illegal character: "Other Windows" It points to a line that has %_cwi_%.WheelSearchMode()

I typed that manually so, if it's wrong it's my fault. This comes up every time I try to scroll. This is a large file I'm searching, 55000+ lines, if that makes a difference.

One other thing (hopefully it's not there and I missed it), it would be great if you could click a line in the Text Filter screen results and hit a hotkey and have it open said file and navigate to that line in the physical file.

Thanks again for this great script.
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

07 Jun 2021, 13:09

Kellyzkorner_NJ wrote:
07 Jun 2021, 12:12
Hello, Just to let you know, I'm trying the most recent C version, I'm getting an, Error: The following variable name contains an illegal character: "Other Windows" It points to a line that has %_cwi_%.WheelSearchMode()
hi @Kellyzkorner_NJ, thank you for letting me know the error. i myself did hit this a few times during testing, but can never repeat it immediately. are you having this problem consistently?

by the way, the _cwi_ (current window id) global variable is set by the wm_activate function via onmessage. the "other windows" message simply means the active window is other than those created from the "tf" object (the text filter and its associated windows).
Kellyzkorner_NJ wrote:
07 Jun 2021, 12:12
I typed that manually so, if it's wrong it's my fault. This comes up every time I try to scroll. This is a large file I'm searching, 55000+ lines, if that makes a difference.
i believe 55000+ lines isn't a problem.
Kellyzkorner_NJ wrote:
07 Jun 2021, 12:12
One other thing (hopefully it's not there and I missed it), it would be great if you could click a line in the Text Filter screen results and hit a hotkey and have it open said file and navigate to that line in the physical file.
i'm actually working on a similar feature right now. i call it "context peeking". don't know if it can fit your need though.
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

10 Jun 2021, 00:23

star-one

added a new feature, context peeking, which provides an integrated way of reviewing the context of any lines in your shortlisted result.

firstly, it needs the line numbers as the integration keys. so, it's only available when line numbering is turned on.

it shows a sub-window which contains all the lines from the origin. you double-click/triple-click a line (or select some text out of a line) and the sub-window will be scrolled automatically to show the line at the middle (as much as possible) of the box.

four buttons are provided, in which, "maximize" and "restore" are toggles, just give them a try to see what they toggle about.

when finished, you may just minimize it instead of closing it. so that, you can "double-click" whenever needed. and the sub-window will be brought up automatically in response.

2021-6-10 star-one-a forgot to test assorted matches and its treeview with context peeking. and they didn't work, as things always do, without particularly sweating over. anyhow, they are fixed now.

2021-6-13 star-one-b added a new feature, made some adjustments, and fixed a bug.

made some adjustments so that the "find next match" still works when the "context peeking" is on.

fixed a bug which occurred when context peeking operates in multi-instance
scenario.

added a new feature, a 3-tier search mechanism. and since i think the way it works may be confusing to some users, it's arranged in an extended class out of "tf". as a result, one has to start from that extended class to use it. for instance, stf.start() applies this 3-tier search mechanism whereas tf.start() remains same as before.

the way how "tf" works, as it always does, is the first tier. the difference happens when it found no matches, it then turns to interpret the search pattern as word(s) with missing characters, and to see if putting flexibility between each character can bring some matches. that's the second tier. if it still found no matches, it therefore turns to assume there is not only missing characters but also some characters may be miss placed, hence to see if putting flexibility in characters order can bring some matches. that's the third tier.

for example, try "ien" and "rnf" with the poem. although both bring you this line "And that has made all the difference." the former is a tier 2 result whereas the later is a tier 3 result.

that's it for now.

Code: Select all

SetWorkingDir %A_ScriptDir%
RE_Dll := DllCall("LoadLibrary", "Str", "Msftedit.dll", "Ptr")
thePoem=
(
The Road Not Taken
BY ROBERT FROST
Two roads diverged in a yellow wood,
And sorry I could not travel both
And be one traveler, long I stood
And looked down one as far as I could
To where it bent in the undergrowth;
Then took the other, as just as fair,
And having perhaps the better claim,
Because it was grassy and wanted wear;
Though as for that the passing there
Had worn them really about the same,
And both that morning equally lay
In leaves no step had trodden black.
Oh, I kept the first for another day!
Yet knowing how way leads on to way,
I doubted if I should ever come back.
I shall be telling this with a sigh
Somewhere ages and ages hence:
Two roads diverged in a wood, and I—
I took the one less traveled by,
And that has made all the difference.
)
; tf.start("Load Clipboard")
; msgbox
; tf.start("Load File log.txt")
; msgbox
tf.start(thePoem), stf.start(thePoem)
; msgbox
; tf.start()	;from what's leftoff in the object's memory

;multiple instances can be coexisting as below
; w1 := new tf, w1.start("Load Clipboard")
; w2 := new tf, w2.start("Load File log.txt")
; w3 := new tf, w3.start(thePoem)
; msgbox
; w3.start()	;from what's leftoff in the object's memory
Return

Class stf extends tf {
	FlexRxR(ByRef Hays, Nee) {
	 	v := this.ov
		If Resu := RegExReplace(Hays, this.Maker(Nee))
			Return (Resu, this[v].r := Nee)

		Loop, Parse, Nee
		{	If A_Index > 1
				r .= t A_LoopField
			Else r := A_LoopField
			t := RegExMatch(A_LoopField, "\w") ? "\w{0,3}" : ""
		}
		If Resu := RegExReplace(Hays, this.Maker(r))
			Return (Resu, this[v].r := r)

		Loop, Parse, Nee
		{	If A_Index > 1
				r .= t A_LoopField
			Else r := A_LoopField
			t := RegExMatch(A_LoopField, "\w") ? "[\w'\-+]*" : ""
		}
		If Resu := RegExReplace(Hays, this.Maker(r))
			Return (Resu, this[v].r := r)

		Loop, Parse, Nee, % ("", r := "")
			r .= RegExMatch(A_LoopField, "\w") ? "(?=[\w'\-+]*" A_LoopField ")" : ""
		If StrLen(r)
			r .= "\b[\w'\-+]*\b"
		Else Return ""
		If Resu := RegExReplace(Hays, this.Maker(r))
			Return (Resu, this[v].r := r)
		Else Return ("", this[v].r := "")
	}
}	;end of class

FocusedControl() {
	ControlGetFocus, foco, A
	Return foco
}
#If, WinActive("The Text Filter") and FocusedControl() = "Edit1"
	`::
	Tab::ControlFocus, RICHEDIT50W1, A
#If, WinActive("The Text Filter") and FocusedControl() = "RICHEDIT50W1"
	Tab::ControlFocus, Edit1, A
	Space::PgDn
	+Space::PgUp
	s::Home
	e::End
	t::^Home
	b::^End
	-::%_cwi_%.DecrFontSize()
	+::
	=::%_cwi_%.IncrFontSize()
	`::
	Enter::%_cwi_%.TakeThisLine()
	f::%_cwi_%.FindNextMatch()
	+f::%_cwi_%.FindNextMatch(1)
#If, WinActive("TreeView Of Assorted Matches") and FocusedControl() = "SysTreeView321"
	`::
	Enter::%_cwi_%.tvButtonEnter()
#If, WinActive("Context Peeking") and FocusedControl() = "RICHEDIT50W1"
	~WheelUp::
	~WheelDown::%_cwi_%.WheelSearchMode()
#IfWinActive, The Text Filter
	F1::%_cwi_%.ToggleHelpPage(_cwi_)
	~WheelUp::
	~WheelDown::%_cwi_%.WheelSearchMode()
	!a::%_cwi_%.ShowAssMatches()
	^t::%_cwi_%.AssTreeView(_cwi_)
	^p::%_cwi_%.ContextPeek(_cwi_)
	!v::Gosub, PasteHaySub
	^o::Gosub, OpenFileSub
	^r::%_cwi_%.ReloadFile()
	~RButton::%_cwi_%.RClickMenu()
#IfWinActive

PasteHaySub:
	If _uii_ := %_cwi_%.PasteIntoHaystack()
		%_uii_% := new tf, %_uii_%.start("Load Clipboard")
	Return

OpenFileSub:
	If _OpFi_ := %_cwi_%.OpenFile()
	{	RegExMatch(_OpFi_, "^\w+(?=\t)", _uii_), RegExMatch(_OpFi_, "\t\K.+$", _usf_)
		%_uii_% := new tf, %_uii_%.start("Load File " _usf_)
	}
	Return

WM_ACTIVATE(wParam) {
	global _cwi_
	If (wParam > 0)
		If WinActive("The Text Filter") or WinActive("TreeView Of Assorted Matches") or WinActive("Context Peeking")
		{	WinGetTitle, t, A
			If RegExMatch(t, "\| *?(\w*?) *?\|", m) and StrLen(m1)
				_cwi_ := m1
			Else _cwi_ := "Oops..."
		}Else _cwi_ := "Other Windows"
}
EventHandler(wParam, lParam, msg, hwnd) {
	static lbdowntime := 0
	global lbduration := 0
	If msg = 0x201	;WM_LBUTTONDOWN
		lbdowntime := A_TickCount
	Else If msg = 0x202	;WM_LBUTTONUP
		lbduration := A_TickCount - lbdowntime
	Return
}

Class tf {
	static o := {}
	v := {}

	start(Param := "") {
		OnMessage(0x6, "WM_ACTIVATE")
		OnMessage(0x201, "EventHandler")	;WM_LBUTTONDOWN
		OnMessage(0x202, "EventHandler")	;WM_LBUTTONUP
		this.id := this.GetObjVarNam(), v := this.ov := (this.id = this.__class ? "o" : "v")
		this[v].Version := "Star-One-B"
		If StrLen(Param)
		{	this[v].SelectedFile := ""
			If Param = Load Clipboard
				this[v].Haystack := Clipboard
			Else If SubStr(Param, 1, 9) = "Load File"
				this[v].Haystack := this.FileRead(SubStr(Param, 11))
			Else this[v].Haystack := Param
			this[v].Haystack := StrReplace(this[v].Haystack, "`r`n", "`n")

			this[v].Fayt := this[v].Case := this[v].Wwrap := True, this[v].Whole := this[v].Linum := False, this[v].n := 1, this[v].Hs[0] := this[v].GetAddress("Haystack"), this[v].Hs[1] := this[v].Haystack
		}
		this.BuildGui(this.id), WM_ACTIVATE(1), this.CleanUp(this.id)
	}
	FileRead(theFile) {
		FileRead, theHays, % this[this.ov].SelectedFile := theFile
		Return theHays
	}
	HsP(n) {
		If (n > 1)
			Return this[this.ov].Hs[n - 1]
		Else Return StrGet(this[this.ov].Hs[0])
	}
	NeeP(n) {
		If (n > 1)
			Return this[this.ov].Needle[n - 1]
		Else Return ""
	}
	GetObjVarNam() {
		r := "(\w+?):.+?\{address: " Format("0x{:X}", &this) "\}"
		Return RegExMatch(this.ListGlobalVars(), r, m) ? m1 : ""
	}

BuildGui(id) {
	global NeedleCB
	static Fayt, Case, Whole, Linum, Wwrap
	v := this.ov

	Gui, %id%:New, +Labeltf.Gui_On
	Gui, +Delimiter`n
	Gui, +Resize +MinSize +MaximizeBox
	Gui, Font, s10, Arial New
	Gui, Add, Text,, O p t i o n s:

	begFayt := this[v].Fayt ? "Checked" : ""
	Gui, Add, Checkbox, ym vFayt %begFayt% Tabstop, Instant Filter (aka Find As You Type)
	OnSuch := this.Fayt_OnToggle.Bind(this)
	GuiControl +g, Button1, % OnSuch

	begCase := this[v].Case ? "Checked" : ""
	Gui, Add, Checkbox, ym vCase %begCase% Tabstop, Case Insensitive
	OnSuch := this.Case_OnToggle.Bind(this)
	GuiControl +g, Button2, % OnSuch

	begWhole := this[v].Whole ? "Checked" : ""
	Gui, Add, Checkbox, ym vWhole %begWhole% Tabstop, Whole Word
	OnSuch := this.Whole_OnToggle.Bind(this)
	GuiControl +g, Button3, % OnSuch

	begLinum := this[v].Linum ? "Checked" : ""
	Gui, Add, Checkbox, ym vLinum %begLinum% Tabstop, Line Number
	OnSuch := this.Linum_OnToggle.Bind(this)
	GuiControl +g, Button4, % OnSuch

	beg_Wrap := this[v].Wwrap ? "Checked" : ""
	Gui, Add, Checkbox, ym vWwrap %beg_Wrap% Tabstop, Word Wrap
	OnSuch := this.Wwrap_OnToggle.Bind(this)
	GuiControl +g, Button5, % OnSuch

	Gui, Font, s18, Arial New
	Gui, Add, Text, xm Section, Filter:

	w := A_ScreenWidth - 330
	Gui, Add, ComboBox, w%w% ys vNeedleCB +hwndhEdit1
	OnSuch := this.Fpat_OnTyping.Bind(this)
	GuiControl +g, %hEdit1%, % OnSuch

	Gui, Add, Button, h38 ys Default, Go
	OnSuch := this.Go_OnClick.Bind(this)
	GuiControl +g, Button6, % OnSuch

	Gui, Font, s10, Arial New
	Gui, Add, Button, h38 ys, Refresh
	OnSuch := this.Refresh_OnClick.Bind(this)
	GuiControl +g, Button7, % OnSuch

	Gui, Font, s18, Arial New
	Gui, Add, Button, h38 ys, Back
	OnSuch := this.Back_OnClick.Bind(this)
	GuiControl +g, Button8, % OnSuch

	h := A_ScreenHeight - 143, w := A_ScreenWidth - 30, begWwrap := this[v].Wwrap ? "+Wrap" : "+HScroll"
	Gui, Font, s18, Consolas
	Gui, Add, Custom, ClassRICHEDIT50W x10 h%h% w%w% %begWwrap% +VScroll +hwndHRE +0x804	;ES_READONLY = 0x800, ES_MULTILINE = 0x4
	OnSuch := this.RE_OnEvent.Bind(this)
	GuiControl +g, %HRE%, % OnSuch

	h := A_ScreenHeight - 64, w := A_ScreenWidth - 6
	Gui, Show, h%h% w%w%, The Text Filter

	this.HRE := HRE, this.RE := this.GetTomDoc(HRE)
	this.RenewGui()	;show content
}

RE_OnEvent() {
	v := this.ov
	If this[v].Linum and WinActive("The Text Filter") and WinExist("Context Peeking  |  " this.id) and not this[v].eFlag and StrLen(this.RE.Selection.Text)
	{	this[v].eFlag := True
		n := this[v].n
		slcs := this.RE.Selection.Start, slce := this.RE.Selection.End, SelBegChar := this.RE.Range(slcs, slcs + 1).Text, SelEndChar := this.RE.Range(slce - 1, slce).Text, Hays := this.RE.Range(0, 999999999).Text
		If InStr(A_Priorkey, "WheelUp")
			this.RE.Range(slcs, slcs).Select
		Else maybe := InStr("`r`n", SelEndChar) ? 1 : 0, this.RE.Range(slce - maybe, slce - maybe).Select
		Sleep, 500
		maybe := InStr("`r`n", SelBegChar) and (SelEndChar = A_Space) ? 2 : 1, mpos := this.ULoNLoCR(Hays, slcs + maybe)
		slcs := mpos, mpos := this.DLoNLoCR(Hays, slce + 1)
		this.RE_PeekContext(mpos > StrLen(Hays) ? SubStr(Hays, slcs) : SubStr(Hays, slcs, mpos - slcs))
		this[v].eFlag := False
	}
}
RE_PeekContext(theOne) {
	cpLnum := RegExReplace(theOne, "^\t? *?(\d+?):.*$", "$1")
	If cpLnum is digit
		Hays := this[this.ov].HaystackN, cpLend := RegExMatch(RegExReplace(Hays, "`n",,, cpLnum - 1), "`n") + cpLnum - 1, mpos := this.ULoNLoCR(Hays, cpLend), cpLbeg := mpos - 1, this.CP.Range(cpLbeg, cpLend).Select, this.cpScroIntoView()
}

Gui_OnSize() {
	If A_EventInfo = 1
		Return
	GuiControl, Move, RICHEDIT50W1, % "h" (A_GuiHeight - 95) " w" (A_GuiWidth - 14)
}

Gui_OnEscape() {
	Gui, %A_Gui%:Destroy
	IfExist, tf_%A_Gui%Work.rtf
		FileDelete, tf_%A_Gui%Work.rtf
	IfExist, tfHelp.rtf
		FileDelete, tfHelp.rtf
}

Gui_OnClose() {
	Gui, %A_Gui%:Destroy
	IfExist, tf_%A_Gui%Work.rtf
		FileDelete, tf_%A_Gui%Work.rtf
	IfExist, tfHelp.rtf
		FileDelete, tfHelp.rtf
}

CleanUp(id) {
	IfExist, tf_%id%Work.rtf
		FileDelete, tf_%id%Work.rtf
	IfExist, tfHelp.rtf
		FileDelete, tfHelp.rtf
}

GetTomDoc(HRE) {	;written by "@just me"
	Static IID_ITextDocument := "{8CC497C0-A1DF-11CE-8098-00AA0047BE5D}"
	DocObj := 0
	If DllCall("SendMessage", "Ptr", HRE, "UInt", 0x043C, "Ptr", 0, "PtrP", IRichEditOle, "UInt")	;EM_GETOLEINTERFACE
	{	DocObj := ComObject(9, ComObjQuery(IRichEditOle, IID_ITextDocument), 1)	; ITextDocument
		ObjRelease(IRichEditOle)
	}
	Return DocObj
}

Fayt_OnToggle() {
	v := this.ov, n := this[v].n
	If not (this[v].Fayt := this.Fayt())
		If (this[v].Hs[n] != this.HsP(n))
		{	this[v].Needle[n] := this.Fpat(), theHaystack := this[v].Hs[n]
			n := (this[v].n += 1), this[v].Hs[n] := theHaystack
			this.UpdateTitle()
		}
	ControlFocus, Edit1, A
	this.Fpat_OnTyping()
}

Case_OnToggle() {
	this[this.ov].Case := this.Case()
	ControlFocus, Edit1, A
	this.Fpat_OnTyping()
}

Whole_OnToggle() {
	this[this.ov].Whole := this.Whole()
	ControlFocus, Edit1, A
	this.Fpat_OnTyping()
}

Linum_OnToggle() {
	v := this.ov
	If this[v].Linum := this.Linum()
	{	Progress, zh0 w300 c10 fs18, `nHang On...`n,, Working On Line Numbering, Segoe UI
		lsl := StrLen(this.LineCount())
		HsN =
		Loop, Parse, % this[v].Haystack, `n`r
			HsN .= SubStr(this.StrRepeat(A_Space, lsl) A_Index, 1 - lsl) ":" A_Space A_LoopField "`n"
		Progress, Off
		this[v].HaystackN := HsN, this[v].Hs[0] := this[v].GetAddress("HaystackN")
	}Else this[v].Hs[0] := this[v].GetAddress("Haystack")
	Loop, % this[v].n
		this[v].Hs[A_Index] := IsFunc(this.FlexRxR) ? this.FlexRxR(this.HsP(A_Index), this[v].Needle[A_Index]) : RegExReplace(this.HsP(A_Index), this.Maker(this[v].Needle[A_Index]))
	this.RenewGui(this.Fpat())
}

Wwrap_OnToggle() {
	v := this.ov, this[v].Wwrap := this.Wwrap(), n := this[v].n, this[v].Needle[n] := this.Fpat()
	this.BuildGui(this.id)
;	this.RenewGui(this[v].Needle[n])
	GuiControl,, NeedleCB, % "`n" this[v].NeedleHistory
	ControlSetText, Edit1, % this[v].Needle[n], A
	this.RenewGui(this[v].Needle[n])
	Send, {End}
}

Fpat_OnTyping() {
	v := this.ov
	If not this[v].Fayt and not this[v].CommitLevel
		Return
	Gui, Submit, NoHide
	n := this[v].n, this[v].Needle[n] := theNeedle := this.Fpat()
	If not StrLen(theNeedle) and this[v].CommitLevel
	{	this[v].CommitLevel := False, this.Back_OnClick()
		Progress, Off
		Return
	}
	theHaystack := IsFunc(this.FlexRxR) ? this.FlexRxR(this.HsP(n), theNeedle) : RegExReplace(this.HsP(n), this.Maker(theNeedle))
	If this[v].CommitLevel
	{	If not StrLen(this[v].NeedleHistory)
			this[v].NeedleHistory := theNeedle, Added := True
		Else If not RegExMatch(this[v].NeedleHistory, (this[v].Case ? "i" : "") "m`a)^\Q" theNeedle "\E$")
			this[v].NeedleHistory := theNeedle "`n" this[v].NeedleHistory, Added := True
		Else Added := False
		If Added
		{	GuiControl,, NeedleCB, % "`n" this[v].NeedleHistory
			ControlSetText, Edit1, % theNeedle
		}
		If (theHaystack = this.HsP(n))
			Progress, zh0 w450 c10 fs18, `nResult No Change Thus Not Anew Level`n,, Prompt, Segoe UI
		Else
		{	If not this[v].Fayt
				this[v].Hs[n] := theHaystack
			If (this.LineCount(n) < 2) and not (theNeedle == this.NeeP(n))
				Progress, zh0 w450 c10 fs18, `nUltimate/Empty Result Reached`nThus Level Not Advanced`n,, Prompt, Segoe UI
			Else
			{	n := (this[v].n += 1), this[v].Hs[n] := theHaystack
				Send, ^a
			}
		}
		this[v].CommitLevel := False
		Sleep, 500
		Progress, Off
	}Else this[v].Hs[n] := theHaystack
	this.RenewGui(theNeedle)
	If not this[v].Fayt and not StrLen(this[v].Hs[n])
	{	Progress, zh0 w450 c10 fs18, `nEmpty Result Reached`n`nFalling Back Now`n,, Prompt, Segoe UI
		Sleep, 1000
		this[v].Hs[n] := this.HsP(n)
		ControlSetText, Edit1, % this.NeeP(n)
		this.RenewGui(this.Fpat())
		Send, ^a
		Progress, Off
	}
}
Maker(t) {
	v := this.ov, t := this.tw4sh(t), r := (this[v].Case ? "i" : "") "m`a)^(?!"
	Loop, Parse, t, % A_Space
		If SubStr(A_LoopField, 1, 1) = "-"
			r .= "(?!.*(" (this[v].Whole ? "\b" : "") SubStr(A_LoopField, 2) (this[v].Whole ? "\b" : "") "))"
		Else r .= "(?=.*(" (this[v].Whole ? "\b" : "") A_LoopField (this[v].Whole ? "\b" : "") "))"
	r .= ").*\R?"
	Return r
}

Go_OnClick() {
	ControlFocus, Edit1, A
	Progress, zh0 w380 c10 fs18, `nCommitting This Level`n,, Prompt, Segoe UI
	this[this.ov].CommitLevel := True, this.Fpat_OnTyping()
}

Refresh_OnClick() {
	this.RenewGui(this[this.ov].Needle[this[this.ov].n] := this.Fpat(), "retain")
}

Back_OnClick() {
	ControlFocus, Edit1, A
	v := this.ov, n := this[v].n, this[v].Needle[n] := this.Fpat()
	If (n = 1) and (this[v].Hs[n] != this.HsP(n))
		this[v].Hs[n] := this.HsP(n), this[v].Needle[n] := ""
	Else If (this[v].Hs[n] = this.HsP(n)) and (this[v].Needle[n] = this.NeeP(n))
		If (n > 2)
			n := (this[v].n -= 2)
		Else If (n = 2)
			n := this[v].n := 1, this[v].Hs[n] := this.HsP(n), this[v].Needle[n] := ""
		Else n := this[v].n := 0
	Else n := (this[v].n -= 1)
	If n
	{	ControlSetText, Edit1, % this[v].Needle[n]
		this.RenewGui(this[v].Needle[n])
		Send, ^a
		If (n = 1) and (this[v].Hs[n] = this.HsP(n))
		{	Progress, zh0 w380 c10 fs18, `nReturned To The Origin`n,, Prompt, Segoe UI
			Sleep, 500
			Progress, Off
		}Else
		{	Progress, zh0 w380 c10 fs18, `nReturned To Level %n%`n,, Prompt, Segoe UI
			Sleep, 500
			Progress, Off
		}
	}Else this.Gui_OnClose()
}

Fpat() {
	GuiControlGet, Fpat,, Edit1
	If not StrLen(Fpat)
		ControlGetText, Fpat, Edit1
	Return Fpat
}
Fayt() {
	GuiControlGet, Fayt,, Button1
	If not StrLen(Fayt)
		ControlGetText, Fayt, Button1
	Return Fayt
}
Case() {
	GuiControlGet, Case,, Button2
	If not StrLen(Case)
		ControlGetText, Case, Button2
	Return Case
}
Whole() {
	GuiControlGet, Whole,, Button3
	If not StrLen(Whole)
		ControlGetText, Whole, Button3
	Return Whole
}
Linum() {
	GuiControlGet, Linum,, Button4
	If not StrLen(Linum)
		ControlGetText, Linum, Button4
	Return Linum
}
Wwrap() {
	GuiControlGet, Wwrap,, Button5
	If not StrLen(Wwrap)
		ControlGetText, Wwrap, Button5
	Return Wwrap
}

RenewGui(theNee := "", AMt := "") {
	global lbduration
	v := this.ov, n := this[v].n
	If this.Fpat() not = theNee
		Goto, WorkInterrupt
	this.ToggleReadOnly(this.HRE, this[v].Wwrap)
	If (AMt = "retain")
		theHays := this.RE.Range(0, 999999999).Text
	Else If StrLen(AMt)
		theHays := this[v][AMt]
	Else theHays := this[v].Hs[n]
	ControlSetText, RICHEDIT50W1, %theHays%, A
	indent := (StrLen(this.LineCount()) + 2) * 10, this.RE.Range(0, StrLen(theHays)).SetIndents(-indent, indent, 0)
	If this.Fpat() not = theNee
		Goto, WIP_Interrupt
	r := this.MakerC(IsFunc(this.FlexRxR) ? this[v].r : theNee)
	If StrLen(theNee) and StrLen(r) and RegExMatch(theHays, r)	;RegExReplace(theHays, r,, mc) and (not this[v].Fayt or AMt = "retain" or mc < StrLen(theNee) * 100)
	{	Progress, zh0 w300 c10 fs18, `nHang On...`n,, Working On Matches Highlighting, Segoe UI
 		mpos := 1, mlen := 0
 		While, (mpos := RegExMatch(theHays, r, nm, mpos + mlen)) and (A_Index < 1000 or lbduration > 1000)
 			mlen := StrLen(nm), this.RE.Range(mpos - 1, mpos + mlen - 1).Font.BackColor := 0x00FFFF, npos := mpos
 		lbduration := 0
 		Progress, Off
	}
	WIP_Interrupt:
	this.ToggleReadOnly(this.HRE, this[v].Wwrap)
	ControlFocus, Edit1, A
	this.UpdateTitle()
	WorkInterrupt:
	If this.Fpat() not = theNee
		this.Fpat_OnTyping()
}

UpdateTitle() {
	WinSetTitle, The Text Filter, % ("", v := this.ov, Version := this[v].Version, id := this.id, n := this[v].n, lc := this.LineCount(n), SelectedFile := this[v].SelectedFile), The Text Filter (Version: %Version%)  |  %id%  |  Level %n%  |  Line Count: %lc%  |  %SelectedFile%
}

LineCount(n := 0) {
	If n is digit
		theHays := this.HsP(n + 1)
	Else theHays := n
	StrReplace(theHays, "`n",, lc), lc += StrLen(theHays) and SubStr(theHays, 0) != "`n" ? 1 : 0
	Return lc
}

ToggleReadOnly(HRE, Wwrap) {
	ControlGet, Style, Style,,, ahk_id %HRE%
	If not (Style & 0x800)
		SendMessage, 0x443, 0, 0xF0F0F0,, ahk_id %HRE%	;tune Down BackgroundColor
     SendMessage, 1101, 1, (Style & 0x800) ? (Wwrap ? 0x44 : 0xC4) : (Wwrap ? 0x844 : 0x8C4),, ahk_id %HRE%	;ES_READONLY = 0x800
	If (Style & 0x800)
		SendMessage, 0x443, 0, 0xFFFFEE,, ahk_id %HRE%	;light Up BackgroundColor
}

MakerC(theNee) {
	theNee := this.tw4sh(theNee), r := "", v := this.ov
	Loop, Parse, theNee, % A_Space
		If SubStr(A_LoopField, 1, 1) != "-"
			r .= "(" (this[v].Whole ? "\b" : "") A_LoopField (this[v].Whole ? "\b" : "") ")|"
	If StrLen(r)
	{	If SubStr(r, 0) = "|"
			r := SubStr(r, 1, StrLen(r) - 1)
		r := (this[v].Case ? "i" : "") "m`a)" r
	}
	Return r
}

IncrFontSize() {
	MouseGetPos, mpx, mpy,, moc
	If (moc not = "RICHEDIT50W1")
		MouseMove, 400, 200
	Send, ^{WheelUp}
	If (moc not = "RICHEDIT50W1")
		MouseMove, mpx, mpy
}

DecrFontSize() {
	MouseGetPos, mpx, mpy,, moc
	If (moc not = "RICHEDIT50W1")
		MouseMove, 400, 200
	Send, ^{WheelDown}
	If (moc not = "RICHEDIT50W1")
		MouseMove, mpx, mpy
}

TakeThisLine() {
	slcs := this.RE.Selection.Start, slce := this.RE.Selection.End, Hays := this.RE.Range(0, 999999999).Text
	If (slce > slcs)
	{	Clipboard := SubStr(Hays, slcs + 1, slce - slcs)
		Progress, zh0 w380 c10 fs18, `nSelection Copied`n,, Prompt, Segoe UI
		Sleep, 500
		Progress, Off
		Return
	}
	mpos := this.ULoNLoCR(Hays, slce + 1)
	slcs := mpos, mpos := this.DLoNLoCR(Hays, slce + 1)
	this.FinalTouch(mpos > StrLen(Hays) ? SubStr(Hays, slcs) : SubStr(Hays, slcs, mpos - slcs))
}
ULoNLoCR(hays, mpos) {
	While, mpos > 1 and not InStr("`r`n", SubStr(hays, mpos - 1, 1))
		mpos--
	Return mpos
}
DLoNLoCR(hays, mpos) {
	pmax := StrLen(hays)
	While, mpos <= pmax and not InStr("`r`n", SubStr(hays, mpos, 1))
		mpos++
	Return mpos
}

FinalTouch(theOne) {
	Clipboard := RegExReplace(theOne, "^\t?(?: *?\d+?: )?(.*)$", "$1")
	MsgBox,,, Has Put This Line Into Clipboard, 3
}

FindNextMatch(Reverse := 0) {
	If StrLen(sPat := this.Fpat())
		r := this.MakerC(IsFunc(this.FlexRxR) ? this[this.ov].r : sPat)
	Else r := "nothing to be matched this"
	slcs := this.RE.Selection.Start, slce := this.RE.Selection.End
	If (slce > slcs) and (RegExMatch(A_ThisHotkey, "^\+?f$") and StrLen(sPat) ? False : not RegExMatch(this.RE.Selection.Text, r))
		r := this.MakerS(this.RE.Selection.Text)
	If StrLen(r) and RegExMatch(Hays := this.RE.Range(0, 999999999).Text, r)
	{	StartHere:
		slcs := this.RE.Selection.Start, slce := this.RE.Selection.End, slcs++, slce++
		cp := Reverse ? slcs : slce
		If Reverse
		{	If cp > 1
			{	mpos := 0, pmax := cp - 1
				Loop
				{	npos := RegExMatch(Hays, r, pm, mpos + 1)
					If npos between 1 and %pmax%
						mpos := npos, nm := pm
					Else Break
				}
				found := (npos > pmax) and mpos > 0
			}
			If not found
			{	mpos := cp - 1, pmax := StrLen(Hays)
				Loop
				{	npos := RegExMatch(Hays, r, pm, mpos + 1)
					If npos between %cp% and %pmax%
						mpos := npos, nm := pm
					Else Break
				}
			}
		}Else mpos := RegExMatch(Hays, r, nm, cp)
		mpos--
		If mpos >= 0
		{	cp := mpos + StrLen(nm)
			this.RE.Range(mpos, cp).Select
		}Else If (mpos < 0) and not Reverse
		{	this.RE.Range(0, 0).Select
			Goto, StartHere
		}
	}
}
MakerS(st) {
	Return (StrLen(st), v := this.ov) ? (this[v].Case ? "i)" : "") (this[v].Whole ? "\b" : "") this.escrx(st) (this[v].Whole ? "\b" : "") : ""
}

WheelSearchMode() {
	MouseGetPos,,,, moc
	If not StrLen(moc) or not InStr("Edit1|RICHEDIT50W1", moc)
	{	IfWinNotActive, The Text Filter
		{	WinActivate % "ahk_id " this[this.ov].ParWin
			WinWaitActive
		}
		ControlFocus, RICHEDIT50W1, A
		this.FindNextMatch(InStr(A_ThisHotkey, "WheelUp"))
	}
}

ShowAssMatches() {
	If this.RE.Range(0, 19).Text = "Assorted Matches:`t("
	{	sPat := this.Fpat()
		If this.RE.Range(19, 31).Text = "Alphabetical"
			this.RenewGui(sPat, "AssHays")
		Else this.RenewGui(sPat, "AAssHays")
		Return
	}
	z := this.ov, n := this[z].n
	If not StrLen(this[z].Hs[n]) or not StrLen(sPat := this.Fpat()) or not StrLen(r := this.MakerC(sPat))
		Return
	r := IsFunc(this.FlexRxR) ? this.MakerC(this[z].r) : r
	Progress, zh0 w300 c10 fs18, `nHang On...`n,, Working On Assortment, Segoe UI
	UniqMatches := [], Seq := 0
	Loop, Parse, % this[z].Hs[n], `n`r
	{	mpos = 1
		worknm:
		npos := RegExMatch(A_LoopField, r, nm, mpos)
		If npos
		{	If UniqMatches.HasKey(nm)
				Ums := UniqMatches[nm], AssHays%Ums% .= InStr(AssHays%Ums%, A_LoopField) ? "" : "`n`t" A_LoopField
			Else
				Seq++, UniqMatches[nm] := Seq, AssHays%Seq% := nm "`n`t" A_LoopField
			mpos := npos + StrLen(nm)
			Goto, worknm
		}
	}
	If Seq
	{	UniqHays := tvAAssHays := "", umNoSort := []
		For k, v in UniqMatches
			UniqHays .= "`n" this.StrRepeat(A_Space, 3) A_Index ".`t" k, tvAAssHays .= "`n" A_Index ". " AssHays%v%, umNoSort[v] ? umNoSort[v].Insert(k) : umNoSort[v] := k
		AAssHays := "Assorted Matches:`t(Alphabetical Order)" UniqHays "`n" this.StrRepeat("-", 50) tvAAssHays
		UniqHays := tvAssHays := ""
		For k, v in umNoSort
			UniqHays .= "`n" this.StrRepeat(A_Space, 3) k ".`t" v, tvAssHays .= "`n" A_Index ". " AssHays%k%
		AssHays := "Assorted Matches:`t(Chronological Order)" UniqHays "`n" this.StrRepeat("-", 50) tvAssHays
		this[z].AssHays := AssHays, this[z].AAssHays := AAssHays, this[z].tvAssHays := tvAssHays, this[z].tvAAssHays := tvAAssHays
		this.RenewGui(sPat, "AssHays")
	}
	Progress, Off
}

AssTreeView(id) {
	If this.RE.Range(0, 19).Text not = "Assorted Matches:`t("
		Return
	parwin := this[v].ParWin := WinExist("A")
	Gui, tv%id%:New, +Owner%parwin%
	Gui, +Resize +MinSize +MaximizeBox
	Gui, +Labeltf.tvGui_On
	Gui, Font, s18, Consolas
	Gui, Add, Text,, % this.RE.Range(19, 31).Text = "Alphabetical" ? "Assorted Matches: (Alphabetical Order)" : "Assorted Matches: (Chronological Order)"
	;Gui, Add, Button, Hidden Default, Enter
	Gui, Add, TreeView, AltSubmit Section xm w545
	OnSuch := this.tvEventHdlr.Bind(this)
	GuiControl +g, SysTreeView321, % OnSuch
	Seq := 0, aHays := this.RE.Range(19, 31).Text = "Alphabetical" ? this[this.ov].tvAAssHays : this[this.ov].tvAssHays, lc := this.LineCount(aHays) - 1
	Loop, Parse, aHays, `n`r
		If StrLen(A_LoopField)
			If RegExMatch(A_LoopField, "^ *\d+\.[ \t]")
				Seq++, P%Seq% := TV_Add(A_LoopField), Num := 0
			Else Num++, P%Seq%C%Num% := TV_Add(A_LoopField, P%Seq%)
	Gui, Show, x97 y539 h283, TreeView Of Assorted Matches  |  %id%  |  Line Count: %lc%
	GuiControl, Focus, SysTreeView321
}

tvGui_OnSize() {
	If A_EventInfo = 1
		Return
	GuiControl, Move, SysTreeView321, % "h" (A_GuiHeight - 76) " w" (A_GuiWidth - 46)
}

tvGui_OnEscape() {
	Gui, %A_Gui%:Destroy
}

tvGui_OnClose() {
	Gui, %A_Gui%:Destroy
}

tvEventHdlr() {
	If InStr("DoubleClick|S|K|Normal", A_GuiEvent)
		TV_GetText(theOne, TV_GetSelection())
	this[this.ov].theOne := theOne
	If A_GuiEvent = DoubleClick
		IfWinExist, % "Context Peeking  |  " this.id
			this.RE_PeekContext(theOne)
		Else this.tvButtonEnter()
}

tvButtonEnter() {
	this.FinalTouch(this[this.ov].theOne)
}

PasteIntoHaystack() {
	global _cwi_
	If StrLen(Clipboard)
	{	InputBox, id, Object Instance Identification, No space is allowed and please keep it short. Accept the given ID means not to create a new window but replace the content of the current one.,,,,,,,, % _cwi_
		If not ErrorLevel and StrLen(id)
		{	If RegExMatch(id, "\W")
				id := RegExReplace(id, "\W")
			If (id = _cwi_)
				this.start("Load Clipboard")
			Else Return id
		}
	}Else MsgBox, Clipboard is empty
}

OpenFile() {
	global _cwi_
	FileSelectFile, SelectedFile
	If StrLen(SelectedFile)
	{	InputBox, id, Object Instance Identification, No space is allowed and please keep it short. Accept the given ID means not to create a new window but replace the content of the current one.,,,,,,,, % _cwi_
		If not ErrorLevel and StrLen(id)
		{	If RegExMatch(id, "\W")
				id := RegExReplace(id, "\W")
			If (id = _cwi_)
				this.start("Load File " SelectedFile), this[this.ov].SelectedFile := SelectedFile
			Else Return id "`t" SelectedFile
		}
	}
}

ReloadFile() {
	If StrLen(SelectedFile := this[this.ov].SelectedFile)
		this.start("Load File " SelectedFile)
}

RClickMenu() {
	global lbduration
	MouseGetPos, xb4, yb4
	While, GetKeyState("RButton", "P")
		Continue
	MouseGetPos, rbx, rby, mow, moc
	If Abs(rbx - xb4) > 10 or Abs(rby - yb4) > 10
		Return
	If (mow = (HGUI := WinExist("A"))) and rby > 30
		If (moc = "Button8")
			this.ShowTrail()
		Else If (moc not = "Edit1")
		{	Menu, myMenu, Add
			Menu, myMenu, DeleteAll
			Menu, myMenu, Add, Show Assorted Matches, MenuHandler
			If this.RE.Range(0, 19).Text = "Assorted Matches:`t("
				Menu, myMenu, Add, Put Assorted Matches In TreeView, MenuHandler
			If this[this.ov].Linum and not WinExist("Context Peeking  |  " this.id)
			{	Menu, myMenu, Add
				Menu, myMenu, Add, Context Peeking, MenuHandler
			}
			Menu, myMenu, Add
			Menu, myMenu, Add, Increase Font Size, MenuHandler
			Menu, myMenu, Add, Decrease Font Size, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, Paste Into The Base, MenuHandler
			Menu, myMenu, Add, Open A File, MenuHandler
			Menu, myMenu, Add, Reload The File, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, List Global Variables, MenuHandler
			If (moc = "RICHEDIT50W1")
			{	Menu, myMenu, Add
				Menu, myMenu, Add, Copy The Line/Selection, MenuHandler
				Menu, myMenu, Add, Go To Rightmost Of The Line, MenuHandler
				Menu, myMenu, Add, Go To Leftmost Of The Line, MenuHandler
				Menu, myMenu, Add, Go To The Bottom, MenuHandler
				Menu, myMenu, Add, Go To The Top, MenuHandler
				Menu, myMenu, Add, Page Down, MenuHandler
				Menu, myMenu, Add, Page Up, MenuHandler
			}
			Menu, myMenu, Add
			Menu, myMenu, Add, Downward Find Next Match, MenuHandler
			Menu, myMenu, Add, Upward Find Next Match, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, Override The 1K Default, MenuHandler
			Menu, myMenu, Add
			Menu, myMenu, Add, Show/Hide Help Page, MenuHandler
			Menu, myMenu, Show
		}
	Return
	MenuHandler:
		WinWaitActive, ahk_id %HGUI%
		If A_ThisMenuItem = Show Assorted Matches
			this.ShowAssMatches()
		Else If A_ThisMenuItem = Put Assorted Matches In TreeView
			this.AssTreeView(this.id)
		Else If A_ThisMenuItem = Context Peeking
			this.ContextPeek(this.id)
		Else If A_ThisMenuItem = Increase Font Size
			this.IncrFontSize()
		Else If A_ThisMenuItem = Decrease Font Size
			this.DecrFontSize()
		Else If A_ThisMenuItem = Paste Into The Base
			Gosub, PasteHaySub
		Else If A_ThisMenuItem = Open A File
			Gosub, OpenFileSub
		Else If A_ThisMenuItem = Reload The File
			this.ReloadFile()
		Else If A_ThisMenuItem = List Global Variables
			this.start(this.ListGlobalVars())
		Else If A_ThisMenuItem = Copy The Line/Selection
		{	If this.RE.Selection.End = this.RE.Selection.Start
				MouseClick, Left, rbx, rby
			this.TakeThisLine()
		}Else If A_ThisMenuItem = Go To Rightmost Of The Line
		{	MouseClick, Left, rbx, rby
			Send, {End}
		}Else If A_ThisMenuItem = Go To Leftmost Of The Line
		{	MouseClick, Left, rbx, rby
			Send, {Home}
		}Else If A_ThisMenuItem = Go To The Bottom
		{	MouseClick, Left, rbx, rby
			Send, ^{End}
		}Else If A_ThisMenuItem = Go To The Top
		{	MouseClick, Left, rbx, rby
			Send, ^{Home}
		}Else If A_ThisMenuItem = Page Down
		{	MouseClick, Left, rbx, rby
			Send, {PgDn}
		}Else If A_ThisMenuItem = Page Up
		{	MouseClick, Left, rbx, rby
			Send, {PgUp}
		}Else If A_ThisMenuItem = Downward Find Next Match
		{	ControlFocus, RICHEDIT50W1, A
			this.FindNextMatch()
		}Else If A_ThisMenuItem = Upward Find Next Match
		{	ControlFocus, RICHEDIT50W1, A
			this.FindNextMatch(1)
		}Else If A_ThisMenuItem = Override The 1K Default
			lbduration := 1234, this.RenewGui(this.Fpat(), "retain")
		Else If A_ThisMenuItem = Show/Hide Help Page
			this.ToggleHelpPage(this.id)
		Return
}

ShowTrail() {
	HGUI := WinExist("A"), v := this.ov, n := this[v].n
	Menu, myTrail, Add
	Menu, myTrail, DeleteAll
	While, A_Index < n
	{	tn := n - A_Index, theNeedle := this[v].Needle[tn], lc := this.LineCount(tn)
		Menu, myTrail, Add, Level &%tn% remains %lc% lines after this filter: %theNeedle%, BtsLevel
	}
	lc := this.LineCount()
	Menu, myTrail, Add, Origin %lc% lines (Level &0), BtsLevel
	Menu, myTrail, Show
	Return
	BtsLevel:
		WinWaitActive, ahk_id %HGUI%
		n -= A_ThisMenuItemPos
		If n < 1
			n := this[v].n := 1, this[v].Hs[n] := this.HsP(n), this[v].Needle[n] := ""
		Else this[v].n := n
		ControlSetText, Edit1, % this[v].Needle[n]
		this.RenewGui(this[v].Needle[n])
		Return
}

ToggleHelpPage(id) {
	theFile = tf_%id%Work.rtf
	If this.RE.Range(0, 17).Text = "H e l p _ P a g e"
		IfExist, %theFile%
			this.RE.Open(theFile, 0x01, 0)
		Else this.RenewGui(this.Fpat())
	Else
	{	IfExist, %theFile%
			this.RE.Save(0, 0, 0)
		Else this.RE.Save(theFile, 0x01, 0)
		IfExist, tfHelp.rtf
			FileDelete, tfHelp.rtf
		FileAppend, % this.HelpPage(), tfHelp.rtf
		this.RE.Open("tfHelp.rtf", 0x01, 0)
	}
}

ContextPeek(id) {
	v := this.ov
	If not this[v].Linum
		Return
	parwin := this[v].ParWin := WinExist("A")
	Gui, cp%id%:New, +Owner%parwin%
	Gui, +Resize +MinSize +MaximizeBox
	Gui, +Labeltf.cpGui_On
	Gui, Font, s12, Arial New
	Gui, Add, Button,, Close
	OnSuch := this.cpBclose.Bind(this)
	GuiControl +g, Button1, % OnSuch
	Gui, Add, Button, ys, Maximize
	OnSuch := this.cpBmaxim.Bind(this)
	GuiControl +g, Button2, % OnSuch
	Gui, Add, Button, ys, Restore
	OnSuch := this.cpBresto.Bind(this)
	GuiControl +g, Button3, % OnSuch
	Gui, Add, Button, ys, Minimize
	OnSuch := this.cpBminim.Bind(this)
	GuiControl +g, Button4, % OnSuch
	Gui, Font, s18, Consolas
	Gui, Add, Custom, ClassRICHEDIT50W xm Section +Wrap +VScroll +hwndHCP +0x804	;ES_READONLY = 0x800, ES_MULTILINE = 0x4
	OnSuch := this.cpEventHdlr.Bind(this)
	GuiControl +g, RICHEDIT50W1, % OnSuch
	x := A_ScreenWidth/2, h := A_ScreenHeight - 94, w := A_ScreenWidth - 6, lc := this.LineCount()
	Gui, Show, x%x% y51 h%h% w%w%, Context Peeking  |  %id%  |  Line Count: %lc%
	Progress, zh0 w300 c10 fs18, `nHang On...`n,, Loading Origin, Segoe UI
	GuiControl,, RICHEDIT50W1, % this[v].HaystackN
	GuiControl, Focus, RICHEDIT50W1
	this.HCP := HCP, this.CP := this.GetTomDoc(HCP)
	this.ToggleReadOnly(this.HCP, this[v].Wwrap)
	indent := (StrLen(lc) + 2) * 10, this.CP.Range(0, StrLen(this[v].HaystackN)).SetIndents(-indent, indent, 0)
	SendMessage, 0x443, 0, 0xF0F0F0,, ahk_id %HCP%	;tune Down BackgroundColor
	this.ToggleReadOnly(this.HCP, this[v].Wwrap)
	Progress, Off
}

cpScroIntoView() {
	GuiControlGet, gCPg, Pos, % this.HCP
	this.CP.Selection.ScrollIntoView(0), slcs := this.CP.Selection.Start, slce := this.CP.Selection.End
	this.CP.Range(cp := this.CfromP(this.HCP, 1, this.PfromC(this.HCP, slcs) + gCPgH / 2), cp).Select
	WinActivate, % "Context Peeking  |  " this.id
	WinWaitActive
	this.CP.Range(slcs, slce).Select
}
CfromP(hE, x := -1, y := -1) {
	VarSetCapacity(RECT, 16, 0)
	SendMessage 0xB2,, &RECT,, ahk_id %hE%	;EM_GETRECT
	LefP := NumGet(RECT, 0, "Int"), TopP := NumGet(RECT, 4, "Int"), RigP := NumGet(RECT, 8, "Int"), BotP := NumGet(RECT, 12, "Int")
	VarSetCapacity(PoL, 8, 0), NumPut(x < 0 ? RigP : x, PoL,, "Int"), NumPut(y < 0 ? BotP : y, PoL, 4, "Int")
	SendMessage 0xD7,, &PoL,, ahk_id %hE%	;EM_CHARFROMPOS
	Return ErrorLevel
}
PfromC(hE, i) {
	VarSetCapacity(PoL, 8, 0)
	SendMessage 0xD6, &PoL, i,, ahk_id %hE%	;EM_POSFROMCHAR
	Return NumGet(PoL, 4, "Int")
}

cpGui_OnSize() {
	If A_EventInfo = 1
		Return
	GuiControl, Move, RICHEDIT50W1, % "h" (A_GuiHeight - 65) " w" (A_GuiWidth - 30)
}

cpGui_OnEscape() {
	Gui, %A_Gui%:Destroy
}

cpGui_OnClose() {
	Gui, %A_Gui%:Destroy
}

cpBclose() {
	Gui, %A_Gui%:Destroy
}

cpBmaxim() {
	WinGet, wMM, MinMax, A
	If wMM = 1	;already maximized
		WinRestore, A
	Else WinMaximize, A
	GuiControl, Focus, RICHEDIT50W1
}

cpBresto() {
	static x := A_ScreenWidth/2, h := A_ScreenHeight - 94, w := A_ScreenWidth - 6
	WinGet, wMM, MinMax, A
	If wMM = 0	;already restored
	{	WinGetPos, GuiX,,,, A
		If (GuiX + x >= A_ScreenWidth)
			Gui, Show, x-5 y51 h%h% w%w%
		Else Gui, Show, x%x% y51 h%h% w%w%
	}Else WinRestore, A
	GuiControl, Focus, RICHEDIT50W1
}

cpBminim() {
	WinMinimize, A
	GuiControl, Focus, RICHEDIT50W1
}

cpEventHdlr() {
	IfWinActive, Context Peeking
;	{	WinGet, wMM, MinMax, A
;		If wMM > -1	;not minimized
		{	Sleep, 500
			GuiControl, Focus, RICHEDIT50W1
		}
;	}
}

tw4sh(t) {
	tog := "\/", va := "?\\'\w", ph := "[^" va "\n]", tar := "\" tog ph "*?\K[" va "]", ph := "[^\w\n]"
	If mpos:=RegExMatch(t, tar)	;contained toggle (on)
	{	pb := "[^\w\n]", sh := "((?<=^|" ph ")", st := "(?=(\W|$)))"
		skip_c := skip_w := yb := False
		r := StrReplace(SubStr(t, 1, mpos - 1), tog) sh, rr := xx := ""
		Loop
		{	c := SubStr(t, mpos, 1), cc := SubStr(t, mpos + 1, 1)
			If skip_c
				skip_c--
			Else If SubStr(t, mpos, StrLen(tog)) = tog	;toggle (off) encountered
			{	this.flush(r, xx, pb, yb, st), rr := xx := ""
				mpos += StrLen(tog), npos := mpos
				If mpos:=RegExMatch(t, tar,, npos)	;found next toggle (on)
					r .= StrReplace(SubStr(t, npos, mpos - npos), tog) sh, rr := xx := ""
				Else
				{	r .= SubStr(t, npos)
					Break
				}
				Continue
			}Else If RegExMatch(c, "\w") or (c = "?")	;valid character
			{	If rr
					If not skip_w and ((cc not = "/") or StrLen(xx))
						this.flush(r, xx, pb, yb)
					Else If (cc = "/") and not StrLen(xx)
					{	If SubStr(t, mpos + 2, 1) = "/"
							skip_w := skip_w ? skip_w : True, skip_c++
						skip_c++
					}Else{}
				Else If StrLen(rr)
					r .= xx sh, rr := xx := ""
				r .= (c = "?") ? (skip_w ? "\w" : ("\w+?", yb++)) : c (skip_w ? "" : ("\w*?", yb++)), skip_w -= skip_w ? 1 : 0, rr := True
			}Else If (c = A_Space) and rr
				this.flush(r, xx, pb, yb, st), xx := A_Space, rr := 0
			Else If (c = "'")
				xx .= "\W"
			Else If (c = "\") and skip_w not < 0
				If (cc == "Q")
					skip_c++, skip_w := -1, this.flush(r, xx, pb, yb)
				Else If (cc == "E")
					skip_c++, skip_w := False
				Else If (cc = c)
					xx .= c
				Else If RegExMatch(cc, "\w")
					skip_c++, xx .= cc
				Else skip_c++, xx .= c cc
			Else xx .= c
			mpos++
			If (mpos > StrLen(t))	;exceeded
			{	If rr
					this.flush(r, xx, pb, yb, st), rr := xx := ""
				Break
			}
		}
	}Else r := t
	Return r
}

flush(ByRef r, ByRef xx, pb, ByRef yb, st := "") {
	mb := "[^,;\w\n.?!]+?", ss := SubStr(xx, -1), sc := (ss == "\W") or (ss == "\s") or (ss == "\t") or (ss == "\r") or (ss == "\n"), r .= xx ? (RegExMatch(SubStr(xx, 1, 1), "\w") ? mb : pb "*?") xx (st ? ")" : (RegExMatch(ss, "\\?\w$") and not sc ? "" : pb "*?")) : (st ? st : (yb ? (mb, yb--) : "")), xx := ""
}

StrRepeat(string, times) {
	Loop, %times%
		output .= string
	Return output
}
escrx(h) {
	e := "\().[]*+?{}^$|"

	Loop, Parse, e
		If InStr(h, A_LoopField)
			h := StrReplace(h, A_LoopField, "\" A_LoopField)

	Return h
}
ListGlobalVars() {	;written by Lexikos
	static hwndEdit, pSFW, pSW, bkpSFW, bkpSW

	If !hwndEdit
	{	dhw := A_DetectHiddenWindows
		DetectHiddenWindows, On
		Process, Exist
		ControlGet, hwndEdit, Hwnd,, Edit1, ahk_class AutoHotkey ahk_pid %ErrorLevel%
		DetectHiddenWindows, %dhw%

		astr := A_IsUnicode ? "astr":"str"
		ptr := A_PtrSize=8 ? "ptr":"uint"
		hmod := DllCall("GetModuleHandle", "str", "user32.dll")
		pSFW := DllCall("GetProcAddress", ptr, hmod, astr, "SetForegroundWindow")
		pSW := DllCall("GetProcAddress", ptr, hmod, astr, "ShowWindow")
		DllCall("VirtualProtect", ptr, pSFW, ptr, 8, "uint", 0x40, "uint*", 0)
		DllCall("VirtualProtect", ptr, pSW, ptr, 8, "uint", 0x40, "uint*", 0)
		bkpSFW := NumGet(pSFW+0, 0, "int64")
		bkpSW := NumGet(pSW+0, 0, "int64")
	}

	If (A_PtrSize=8)
	{	NumPut(0x0000C300000001B8, pSFW+0, 0, "int64")	;return TRUE
		NumPut(0x0000C300000001B8, pSW+0, 0, "int64")	;return TRUE
	}Else
	{	NumPut(0x0004C200000001B8, pSFW+0, 0, "int64")	;return TRUE
		NumPut(0x0008C200000001B8, pSW+0, 0, "int64")	;return TRUE
	}

	ListVars

	NumPut(bkpSFW, pSFW+0, 0, "int64")
	NumPut(bkpSW, pSW+0, 0, "int64")

	ControlGetText, text,, ahk_id %hwndEdit%

	RegExMatch(text, "sm)(?<=^Global Variables \(alphabetical\)`r`n-{50}`r`n).*", text)
	Return text
}
HelpPage() {
HelpPage=
(
{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fnil\fcharset0 Arial;}{\f1\fnil\fcharset0 Consolas;}}
{\colortbl ;\red255\green0\blue0;\red155\green0\blue211;\red0\green77\blue187;\red0\green176\blue80;\red0\green0\blue255;}
{\*\generator Riched20 10.0.19041}\viewkind4\uc1 
\pard\sa200\sl276\slmult1\b\fs36\lang9 H e l p _ P a g e\b0\par
\par
\cf1\b F1\cf0\b0  - to toggle this help page\par
\par
\b Basic syntax by example:\b0\par

\pard\li720\sa200\sl276\slmult1 coexisting words/strings/patterns -unwanted\par
for instance, \cf2\b\f1 d.*d -the and -oo \\ws\cf0\b0\f0\par
(the 5 segments (space separated) above can go all at once, and yet, they can also be entered one by one. that's the so-called multilevel approach, instead.)\par

\pard\sa200\sl276\slmult1\par
\b Feature keys:\b0\par

\pard\li720\sa200\sl276\slmult1\cf3\b Alt+a\cf0\b0  - to show assorted matches and toggle between chronological and alphabetical order.\par
\cf3\b Ctrl+t\cf0\b0  - to put assorted matches into a treeview gui which may ease the exploration when the list is long and involved multiple groups.\par
\cf3\b Ctrl+p\cf0\b0  - to bring up context peeking gui which provides an integrated way of reviewing the context of any lines in the shortlisted result.\par
\cf3\b WheelDown\cf0\b0  - to find the next match (downward) from the caret position (given that, the mouse pointer is currently outside both text/edit boxes.)\par
\cf3\b WheelUp\cf0\b0  - to find the next match (upward) from the caret position (given that, the mouse pointer is currently outside both text/edit boxes.)\par
\cf3\b Alt+v\cf0\b0  - to paste clipboard content into the big text/edit box.\par
\cf3\b Ctrl+o\cf0\b0  - to open a file.\par
\cf3\b Ctrl+r\cf0\b0  - to reload the file.\par
\cf3\b Rightclick\cf0\b0  "Back" button - to glance committed levels and select one to fall back to.\par
\cf3\b Esc\cf0\b0  - to quit/exit/leave. same as closing the window.\par
\cf3\b Tab\cf0\b0  - to change focus between the text/edit boxes (just try it.)\par
\par
these below only work when the \ul big\ulnone  text/edit box is in focus:\par

\pard\li1440\sa200\sl276\slmult1\cf3\b f\cf0\b0  - to find the next match (downward) from the caret position.\par
\cf3\b Shift+f\cf0\b0  - to find the next match (upward) from the caret position.\par
\i (note: "the line" refers to the current line, that's where the caret is on.)\i0\par
\cf3\b Enter\cf0\b0  - to copy the line.\par
\cf3\b Space\cf0\b0  - to page down\par
\cf3\b Shift+Space\cf0\b0  - to page up\par
\cf3\b s\cf0\b0  - to move the caret to the beginning/start of the line.\par
\cf3\b e\cf0\b0  - to move the caret to the end of the line.\par
\cf3\b t\cf0\b0  - to move the caret to the top of the text.\par
\cf3\b b\cf0\b0  - to move the caret to the bottom of the text.\par
\cf3\b + or =\cf0\b0  - to increase font size (same as \cf3\b Ctrl+WheelUp\cf0\b0 ).\par
\cf3\b minus "-"\cf0\b0  - to decrease font size (same as \cf3\b Ctrl+WheelDown\cf0\b0 ).\par
\i (side note: "caret" may be known as "cursor" to some people.)\i0\par

\pard\sa200\sl276\slmult1\par
\tab these below only work when the \ul small\ulnone  text/edit box is in focus:\par

\pard\li1440\sa200\sl276\slmult1\cf3\b Enter\cf0\b0  - to commit a level (which makes the current result becoming the base/source of the next level.) same as clicking the button Go.\par

\pard\sa200\sl276\slmult1\par

\pard\li720\sa200\sl276\slmult1\cf3\b Backtick (``)\cf0\b0  - since users mostly use this tool to locate the specific line of text and copy it for whatever subsequent actions to be taken, which typically involves Tab and then Enter (if the first line is the one wanted), backtick is an alternative which can supersede both keys by just hit backtick twice instead.\par

\pard\sa200\sl276\slmult1\par
The \b improvised shorthand\b0  feature:\par

\pard\li720\sa200\sl276\slmult1 this feature can be triggered through "\cf2\b\\/\cf0\b0 " (a \b backslash\b0  followed by a \b slash\b0 ), a "regex like" syntax style, which toggle it on or off. so, enter "\cf2\b\\/tr\cf0\b0 " will bring you those lines contained "\cf4\i the road\cf0\i0 ", "\cf4\i two roads\cf0\i0 ", as well as "\cf4\i them really\cf0\i0 " out of the poem, whereas enter "\cf2\b oo \\/a?a\cf0\b0 " or "\cf2\b\\/a?a\\/ oo\cf0\b0 " will return you these two lines "\cf4\i And looked down one as far as I could\cf0\i0 " and "\cf4\i Then took the other, as just as fair,\cf0\i0 " in which, the "\cf2\b ?\cf0\b0 " represents any valid character regex recognized it as "word" element. Besides, the space has been cared so that the toggle works across it. for instance, enter "\cf2\b\\/tr yw\cf0\b0 " or "\cf2\b\\/yw tr\cf0\b0 " will show this line "\cf4\i Two roads diverged in a yellow wood,\cf0\i0 " where the space is still a separator as always, even though the shorthand feature is toggled on.\par

\pard\sa200\sl276\slmult1\par
\tab three more elements of the shorthand syntax:\par
\par

\pard\fi-360\li1800\sa200\sl276\slmult1 1, \b extra qualifier\b0  (within a single word);\par
2, \b escape character\b0  (between words);\par
3, a single \b character\b0  representing any "\b non-word\b0 " one (between words).\par

\pard\sa200\sl276\slmult1\par

\pard\li720\sa200\sl276\slmult1 the most basic usage of improvised shorthand is to match the specific "phrase" by just entering the first letter of each word constituted it (for instance, "\cf2\b sameto\cf0\b0 " can match "\cf4\i some are more equal than others\cf0\i0 ") yet sometimes we may want to further narrow down the result. if "\cf2\b slh\cf0\b0 " brings you both "\cf4\i she likes him\cf0\i0 " and "\cf4\i she loves him\cf0\i0 ", an extra qualifier to pinpoint one may be useful. it could simply be a "v" for this example hence "\cf2\b slv/h\cf0\b0 " (in which, the "/" after the "v" tells the system that it's just an extra qualifier to the "l", not for a word starts with "v",) returns you "\cf4\i she loves him\cf0\i0 " only. likewise, you may sometimes want to tell the system exactly what the word should be ending with, for instance, if "\cf2\b ioy\cf0\b0 " brings you both "\cf4\i i owe you\cf0\i0 " and "\cf4\i i own you\cf0\i0 ", "\cf2\b ioe//y\cf0\b0 " (in which, the "//" after the "e" tells the system that it's a word ending character, that is, "oe//" represents a word starts with "o" and ends with "e") returns you "\cf4\i i owe you\cf0\i0 " only. and no matter how rare (and impractical) it may be, you may have multiple of these qualifiers for a word, such as, "\cf2\b cm/c/e//d//\cf0\b0 " can match the word "\cf4\i complicated\cf0\i0 " whereas "\cf2\b td/i/u//s//\cf0\b0 " can match the word "\cf4\i tedious\cf0\i0 ".\par
\par
a \b backslash\b0  "\cf2\b\\\cf0\b0 " may be used to escape a single character after it. its target should be a "word" character because you don't need to escape otherwise. for all "non-word" character, just enter them directly except "\\" itself which requires a \b double backslash\b0  "\cf2\b\\\\\cf0\b0 " for a single one. for regex syntax such as "\\s", it becomes "\\\\s" (not "\\\\\\s" you may presume.) in other words, all escaped character will only be matched barely itself. for \ul consecutive\ulnone  "word" characters that need to be escaped, enclose them in the \cf2\b\\Q\cf0\b0  \cf2\b\\E\cf0\b0  pair, similar idea as in regex. one thing to note though, escaped character may affect the word immediately after it, for instance, "\cf2\b z\\xy\cf0\b0 " may bring you "\cf4\i zebra xylophone\cf0\i0 " rather than "\cf4\i zombie x yesterday\cf0\i0 ".\par
\par
an \b apostrophe\b0  "\cf2\b\f1 '\cf0\b0\f0 " may be used to represent any "non-word" character. that is, same as "\\\\W" (the regex syntax "\\W" for a "non-word" character). it may be useful at the (beginning and/or ending) ends of the "phrase" if it can further narrow down the result or you simply want it to be a part of the match.\par

\pard\sa200\sl276\slmult1\par
The \b matches highlighting\b0  feature:\par

\pard\li720\sa200\sl276\slmult1 the \ul default behavior\ulnone  is to highlight no more than a thousand matches (which can improve the response time when too many matches are found.) it can nonetheless be overridden by a long press of your left mouse button on one of the available gui buttons (i.e. go or refresh or back), whereas long press means press it down and hold on for \ul one second or longer\ulnone . therefore, highlighting will cover the whole thing even if the total number of matches is over \ul one thousand\ulnone . this \ul long-press-to-override\ulnone  is not a toggle, it's just an \ul one-off\ulnone  thing, so do it every time you want more than the default.\par

\pard\sa200\sl276\slmult1\par
End of this help page.\par
}
)
	Return HelpPage
}
}	;end of class
Last edited by SundayProgrammer on 13 Jun 2021, 08:19, edited 1 time in total.
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

10 Jun 2021, 12:58

the script is updated. → viewtopic.php?p=404344#p404344 ← click this link

star-one-a fix for omission.

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 104 guests