Text Filter

Post your working scripts, libraries and tools for AHK v1.1 and older
ozzii
Posts: 481
Joined: 30 Oct 2013, 06:04

Re: Text Filter

14 Mar 2021, 10:41

Thank you @SundayProgrammer for your work/updates
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

15 Mar 2021, 02:48

added a new feature which i call it improvised shorthand. that is, you can look up your target (typically a piece of text included one or more words) by just the first letter of each word in it. for instance, the shorthand "tr" out of the poem will bring you those lines contained "the road", "two roads", as well as "them really". (hard to make a longer one since the poem is not long enough. you got the idea anyway, i hope.)

this feature can be triggered through "\/" (a backslash followed by a slash), a "regex like" syntax style, which toggle it on or off. so, enter "\/tr" for the example mentioned above, whereas enter "oo \/a?a" or "\/a?a\/ oo" will return you these two lines "And looked down one as far as I could" and "Then took the other, as just as fair," in which, the "?" 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 "\/tr yw" or "\/yw tr" will show this line "Two roads diverged in a yellow wood," where the space is still a separator as always, even though the shorthand feature is toggled on.

Code: Select all

HelpPage=
(
H e l p _ P a g e

F1 - to toggle this help page

Basic syntax by example:
	coexisting words/strings/patterns -unwanted
	for instance, d.*d -the and -oo \ws
	(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.)

Feature keys:
	Alt+a - to show assorted matches and toggle between chronological and alphabetical order.
	WheelDown - to find the next match (downward) from the caret position (given that, the mouse pointer is currently outside both text/edit boxes.)
	WheelUp - to find the next match (upward) from the caret position (given that, the mouse pointer is currently outside both text/edit boxes.)
	Esc - to quit/exit/leave. same as closing the window.
	Tab - to change focus between the text/edit boxes (just try it.)

	these below only work when the big text/edit box is in focus:
		f - to find the next match (downward) from the caret position.
		Shift+f - to find the next match (upward) from the caret position.
		(note: "the line" refers to the current line, that's where the caret is on.)
		Enter - to copy the line.
		Space - to page down
		Shift+Space - to page up
		s - to move the caret to the beginning/start of the line.
		e - to move the caret to the end of the line.
		t - to move the caret to the top of the text.
		b - to move the caret to the bottom of the text.
		(side note: "caret" may be known as "cursor" to some people.)

	these below only work when the small text/edit box is in focus:
		Enter - to commit a level (which makes the current result becoming the base/source of the next level.) same as clicking the button Go.

	Backtick (``) - 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.

The improvised shorthand feature:
	this feature can be triggered through "\/" (a backslash followed by a slash), a "regex like" syntax style, which toggle it on or off. so, enter "\/tr" will bring you those lines contained "the road", "two roads", as well as "them really" out of the poem, whereas enter "oo \/a?a" or "\/a?a\/ oo" will return you these two lines "And looked down one as far as I could" and "Then took the other, as just as fair," in which, the "?" 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 "\/tr yw" or "\/yw tr" will show this line "Two roads diverged in a yellow wood," where the space is still a separator as always, even though the shorthand feature is toggled on.

End of this help page.
)
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 := 1, regWhole := regLinum := regWwrap := 0
theGui:
Gui, tf:New
Gui, +Delimiter`n
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 - 257
Gui, Add, ComboBox, w%w% ys vNeedleCB gtfFpatHdlr +hwndhEdit1
Gui, Add, Button, h38 ys Default, Go
Gui, Add, Button, h38 ys, Back
h := A_ScreenHeight - 143, w := A_ScreenWidth - 30
begWwrap := regWwrap ? "" : "HScroll"
Gui, Font, s18, Consolas
Gui, Add, Edit, ReadOnly x10 h%h% w%w% %begWwrap% VScroll +hwndhEdit2
Gui, Show,, The Text Filter,, The Text Filter - Level %n%   ( Base %blc% Lines )
ControlGet, cid, hwnd,, Edit2, A
RenewGui:
GuiControl,, Edit2, % Haystack%n%
ControlFocus, Edit1, A
SetWinTitle:
StrReplace(Haystack%n%, "`n",, blc), blc += StrLen(Haystack%n%) and SubStr(Haystack%n%, 0) != "`n" ? 1 : 0
WinSetTitle, The Text Filter,, The Text Filter - Level %n%   ( Base %blc% Lines )
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:
	regWwrap := Wwrap()
	Needle%n% := Fpat()
	Gui, tf:Destroy
	Gosub, theGui
	ControlSetText, Edit1, % Needle%n%, A
	Send, {End}	;SendMessage, 0xB1, cp:=StrLen(Needle%n%), cp,, ahk_id %hEdit1%	;EM_SETSEL
	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 InStr(NeedleHistory, Needle%n%, !regCase)
			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
			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	;SendMessage, 0xB1, 0, StrLen(Needle%pn%),, ahk_id %hEdit1%	;EM_SETSEL
			}
		}
		CommitLevel = 0
		Sleep, 500
		Progress, Off
	}Else Haystack%n% := theHaystack
	Goto, RenewGui
	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
	}
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
	{	Gosub, RenewGui
		ControlSetText, Edit1, % Needle%n%
		Send, ^a	;SendMessage, 0xB1, 0, StrLen(Needle%n%),, ahk_id %hEdit1%	;EM_SETSEL
		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
tfGuiEscape:
tfGuiClose:
	ExitApp

Fpat() {
	ControlGetText, Fpat, Edit1	;GuiControlGet, 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 := "\/", ph := "[^\w\n]", tar := "\" tog ph "*?\K\w"
	If mpos:=RegExMatch(t, tar)	;contained toggle (on)
	{	pb := "[^,\w;\n.]", sh := "((?<=^|" ph ")", st := "(?=(\W|$)))"
		r := StrReplace(SubStr(t, 1, mpos - 1), tog) sh, rr := xx := ""
		Loop
		{	c := SubStr(t, mpos, 1)
			If RegExMatch(c,"\w") or (c = "?")	;valid character
			{	If rr
					r .= xx ? pb "*?" xx pb "*?" : pb "+?", xx := ""
				Else If StrLen(rr)
					r .= xx sh, rr := xx := ""
				r .= (c = "?") ? "\w+?" : c "\w*?", rr := st
			}Else If (c = A_Space) and rr
				r .= xx ? pb "*?" xx rr : rr, xx := A_Space, rr := 0
			Else xx .= "\" c
			mpos++
			If (mpos > StrLen(t))	;exceeded
			{	If rr
					r .= xx ? pb "*?" xx rr : rr, rr := xx := ""
				Break
			}Else If SubStr(t, mpos, StrLen(tog)) = tog	;toggle (off) encountered
			{	r .= xx ? pb "*?" xx rr : rr, 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
				}
			}
		}
	}Else r := t
	;msgbox % t "`n`n" r
	Return r
}

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
	Return foco
}
#If, WinActive("The Text Filter") and FocusedControl() = "Edit1"
	`::
	Tab::ControlFocus, Edit2, A
	~Bs::
	~Del::
		If regFayt
			Goto, tfFpatHdlr
		Else Return
#If, WinActive("The Text Filter") and FocusedControl() = "Edit2"
	Tab::ControlFocus, Edit1, A
	~Space::PgDn
	~+Space::PgUp
	~s::Home
	~e::End
	~t::^Home
	~b::^End
	`::
	~Enter::
		ControlGetText, Hays, Edit2, A
		DllCall("User32\SendMessage", "Ptr", cid, "UInt", 0x00B0, "UIntP", slcs, "UIntP", slce, "Ptr")
		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 := RegExReplace((mpos > pmax ? SubStr(Hays, slcs) : SubStr(Hays, slcs, mpos - slcs)), "^\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
		{	ControlGet, sPat, Selected,, Edit2, A
			r := StrLen(sPat) ? (regCase ? "i)" : "") (regWhole ? "\b" : "") escrx(sPat) (regWhole ? "\b" : "") : ""
		}
		If StrLen(r)
		{	If SubStr(r, 0) = "|"
				r := SubStr(r, 1, StrLen(r)-1)
			ControlGettext, Hays, Edit2, A
			If RegExMatch(Hays, r)
			{	StartHere:
				Gosub, Compute_cp
				If InStr("~WheelUp,~+f", A_ThisHotkey)
				{	Gosub, ReverseWay
					mpos -= 1
				}Else mpos := RegExMatch(Hays, r, nm, cp) - 1
				If mpos >= 0
				{	cp := mpos + StrLen(nm)
					SendMessage, 0xB1, mpos, cp,, ahk_id %hEdit2%	;EM_SETSEL
					SendMessage, 0xB7, 0, 0,, ahk_id %hEdit2%	;EM_SCROLLCARET
				}Else If (mpos < 0) and not InStr("~WheelUp,~+f", A_ThisHotkey)
				{	SendMessage, 0xB1, 0, 0,, ahk_id %hEdit2%	;EM_SETSEL	;Send, ^{Home}
					Goto, StartHere
				}
			}Else MsgBox, Target Not Found
		}
		Return
		MakerFnm:
			sPat := tw4sh(sPat), r := (regCase ? "i" : "") "m`a)"
			Loop, Parse, sPat, % A_Space
				If SubStr(A_LoopField, 1, 1) != "-"
					r .= "(" (regWhole ? "\b" : "") A_LoopField (regWhole ? "\b" : "") ")|"
			Return
		Compute_cp:
			DllCall("User32\SendMessage", "Ptr", cid, "UInt", 0x00B0, "UIntP", slcs, "UIntP", slce, "Ptr"), slcs++, slce++
			cp := InStr("~WheelUp,~+f", A_ThisHotkey) ? 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
#IfWinActive, The Text Filter
	F1::
		ControlGetText, Hays, Edit2, A
		If SubStr(Hays, 1, 17) = "H e l p _ P a g e"
			ControlSetText, Edit2, %B4Help%, A
		Else
		{	B4Help := Hays
			ControlSetText, Edit2, %HelpPage%, A
		}
		Return
	~Wheelup::
	~Wheeldown::
		MouseGetPos,,,, moc
		If not StrLen(moc) or not InStr("Edit1|Edit2", moc)
		{	ControlFocus, Edit2, A
			Goto, NextMatch
		}Else Return
	!a::
		ControlGetText, Hays, Edit2, A
		If SubStr(Hays, 1, 19) = "Assorted Matches:`t("
		{	If SubStr(Hays, 20, 12) = "Alphabetical"
				ControlSetText, Edit2, %AssHays%, A
			Else
				ControlSetText, Edit2, %AAssHays%, A
			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
		If SubStr(r, 0) = "|"
			r := SubStr(r, 1, StrLen(r)-1)
		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 := AssHays := "", umNoSort := []
			For k, v in UniqMatches
				UniqHays .= rn StrRepeat(A_Space, 3) A_Index ".`t" k, AssHays .= rn A_Index ". " AssHays%v%, umNoSort[v] ? umNoSort[v].Insert(k) : umNoSort[v] := k
			AAssHays := "Assorted Matches:`t(Alphabetical Order)" UniqHays rn StrRepeat("-", 50) AssHays
			UniqHays := AssHays := ""
			For k, v in umNoSort
				UniqHays .= rn StrRepeat(A_Space, 3) k ".`t" v, AssHays .= rn A_Index ". " AssHays%k%
			AssHays := "Assorted Matches:`t(Chronological Order)" UniqHays rn StrRepeat("-", 50) AssHays
			ControlSetText, Edit2, %AssHays%, A
		}
		Progress, Off
		Return
#IfWinActive
carno
Posts: 265
Joined: 20 Jun 2014, 16:48

Re: Text Filter

17 Mar 2021, 05:03

I think there is a problem with this line

Code: Select all

Send, ^a 	;SendMessage, 0xB1, 0, StrLen(Needle%pn%),, ahk_id %hEdit1%	;EM_SETSEL
I had to comment out

Code: Select all

Send, ^a
as follows:

Code: Select all

;Send, ^a 	;SendMessage, 0xB1, 0, StrLen(Needle%pn%),, ahk_id %hEdit1%	;EM_SETSEL
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

17 Mar 2021, 08:46

@carno
could you describe the problem you encountered ?
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

20 Mar 2021, 22:31

added three more elements to the shorthand syntax:

1, extra qualifier (within a single word);
2, escape character (between words);
3, a single character representing any "non-word" one (between words).

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, "sameto" can match "some are more equal than others") yet sometimes we may want to further narrow down the result. if "slh" brings you both "she likes him" and "she loves him", an extra qualifier to pinpoint one may be useful. it could simply be a "v" for this example hence "slv/h" (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 "she loves him" only. likewise, you may sometimes want to tell the system exactly what the word should be ending with, for instance, if "ioy" brings you both "i owe you" and "i own you", "ioe//y" (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 "i owe you" only. and no matter how rare (and impractical) it may be, you may have multiple of these qualifiers for a word, such as, "cm/c/e//d//" can match the word "complicated" whereas "td/i/u//s//" can match the word "tedious".

a backslash "\" 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 double backslash "\\" 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 consecutive "word" characters that need to be escaped, enclose them in the \Q \E pair, similar idea as in regex. one thing to note though, escaped character may affect the word immediately after it, for instance, "z\xy" may bring you "zebra xylophone" rather than "zombie x yesterday".

an apostrophe "'" 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.

that's it for now.

Code: Select all

HelpPage=
(
H e l p _ P a g e

F1 - to toggle this help page

Basic syntax by example:
	coexisting words/strings/patterns -unwanted
	for instance, d.*d -the and -oo \ws
	(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.)

Feature keys:
	Alt+a - to show assorted matches and toggle between chronological and alphabetical order.
	WheelDown - to find the next match (downward) from the caret position (given that, the mouse pointer is currently outside both text/edit boxes.)
	WheelUp - to find the next match (upward) from the caret position (given that, the mouse pointer is currently outside both text/edit boxes.)
	Esc - to quit/exit/leave. same as closing the window.
	Tab - to change focus between the text/edit boxes (just try it.)

	these below only work when the big text/edit box is in focus:
		f - to find the next match (downward) from the caret position.
		Shift+f - to find the next match (upward) from the caret position.
		(note: "the line" refers to the current line, that's where the caret is on.)
		Enter - to copy the line.
		Space - to page down
		Shift+Space - to page up
		s - to move the caret to the beginning/start of the line.
		e - to move the caret to the end of the line.
		t - to move the caret to the top of the text.
		b - to move the caret to the bottom of the text.
		(side note: "caret" may be known as "cursor" to some people.)

	these below only work when the small text/edit box is in focus:
		Enter - to commit a level (which makes the current result becoming the base/source of the next level.) same as clicking the button Go.

	Backtick (``) - 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.

The improvised shorthand feature:
	this feature can be triggered through "\/" (a backslash followed by a slash), a "regex like" syntax style, which toggle it on or off. so, enter "\/tr" will bring you those lines contained "the road", "two roads", as well as "them really" out of the poem, whereas enter "oo \/a?a" or "\/a?a\/ oo" will return you these two lines "And looked down one as far as I could" and "Then took the other, as just as fair," in which, the "?" 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 "\/tr yw" or "\/yw tr" will show this line "Two roads diverged in a yellow wood," where the space is still a separator as always, even though the shorthand feature is toggled on.

	three more elements of the shorthand syntax:

	1, extra qualifier (within a single word);
	2, escape character (between words);
	3, a single character representing any "non-word" one (between words).

	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, "sameto" can match "some are more equal than others") yet sometimes we may want to further narrow down the result. if "slh" brings you both "she likes him" and "she loves him", an extra qualifier to pinpoint one may be useful. it could simply be a "v" for this example hence "slv/h" (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 "she loves him" only. likewise, you may sometimes want to tell the system exactly what the word should be ending with, for instance, if "ioy" brings you both "i owe you" and "i own you", "ioe//y" (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 "i owe you" only. and no matter how rare (and impractical) it may be, you may have multiple of these qualifiers for a word, such as, "cm/c/e//d//" can match the word "complicated" whereas "td/i/u//s//" can match the word "tedious".

	a backslash "\" 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 double backslash "\\" 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 consecutive "word" characters that need to be escaped, enclose them in the \Q \E pair, similar idea as in regex. one thing to note though, escaped character may affect the word immediately after it, for instance, "z\xy" may bring you "zebra xylophone" rather than "zombie x yesterday".

	an apostrophe "'" 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.

End of this help page.
)
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 := 1, regWhole := regLinum := regWwrap := 0
theGui:
Gui, tf:New
Gui, +Delimiter`n
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 - 257
Gui, Add, ComboBox, w%w% ys vNeedleCB gtfFpatHdlr +hwndhEdit1
Gui, Add, Button, h38 ys Default, Go
Gui, Add, Button, h38 ys, Back
h := A_ScreenHeight - 143, w := A_ScreenWidth - 30
begWwrap := regWwrap ? "" : "HScroll"
Gui, Font, s18, Consolas
Gui, Add, Edit, ReadOnly x10 h%h% w%w% %begWwrap% VScroll +hwndhEdit2
Gui, Show,, The Text Filter,, The Text Filter - Level %n%   ( Base %blc% Lines )
ControlGet, cid, hwnd,, Edit2, A
RenewGui:
GuiControl,, Edit2, % Haystack%n%
ControlFocus, Edit1, A
SetWinTitle:
StrReplace(Haystack%n%, "`n",, blc), blc += StrLen(Haystack%n%) and SubStr(Haystack%n%, 0) != "`n" ? 1 : 0
WinSetTitle, The Text Filter,, The Text Filter - Level %n%   ( Base %blc% Lines )
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:
	regWwrap := Wwrap()
	Needle%n% := Fpat()
	Gui, tf:Destroy
	Gosub, theGui
	ControlSetText, Edit1, % Needle%n%, A
	Send, {End}	;SendMessage, 0xB1, cp:=StrLen(Needle%n%), cp,, ahk_id %hEdit1%	;EM_SETSEL
	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
			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	;SendMessage, 0xB1, 0, StrLen(Needle%pn%),, ahk_id %hEdit1%	;EM_SETSEL
			}
		}
		CommitLevel = 0
		Sleep, 500
		Progress, Off
	}Else Haystack%n% := theHaystack
	Goto, RenewGui
	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
	}
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
	{	Gosub, RenewGui
		ControlSetText, Edit1, % Needle%n%
		Send, ^a	;SendMessage, 0xB1, 0, StrLen(Needle%n%),, ahk_id %hEdit1%	;EM_SETSEL
		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
tfGuiEscape:
tfGuiClose:
	ExitApp

Fpat() {
	ControlGetText, Fpat, Edit1	;GuiControlGet, 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
			{	r .= xx ? pb "*?" xx st : 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
				r .= xx ? pb "*?" xx st : st, xx := A_Space, rr := 0
			Else If (c = "'")
				flush(r, xx, pb, yb), r .= "\W"
			Else If (c = "\")
				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)
					flush(r, xx, pb, yb), r .= c
				Else If RegExMatch(cc,"\w")
					skip_w := skip_w ? skip_w : True, flush(r, xx, pb, yb)
				Else skip_c++, flush(r, xx, pb, yb), r .= c cc
			Else xx .= c
			mpos++
			If (mpos > StrLen(t))	;exceeded
			{	If rr
					r .= xx ? pb "*?" xx st : st, rr := xx := ""
				Break
			}
		}
	}Else r := t
	;msgbox % t "`n`n" r
	Return r
}
flush(ByRef r, ByRef xx, pb, ByRef yb) {
	r .= xx ? pb "*?" xx pb "*?" : (yb ? (pb "+?", 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
	Return foco
}
#If, WinActive("The Text Filter") and FocusedControl() = "Edit1"
	`::
	Tab::ControlFocus, Edit2, A
	~Bs::
	~Del::
		If regFayt
			Goto, tfFpatHdlr
		Else Return
#If, WinActive("The Text Filter") and FocusedControl() = "Edit2"
	Tab::ControlFocus, Edit1, A
	~Space::PgDn
	~+Space::PgUp
	~s::Home
	~e::End
	~t::^Home
	~b::^End
	`::
	~Enter::
		ControlGetText, Hays, Edit2, A
		DllCall("User32\SendMessage", "Ptr", cid, "UInt", 0x00B0, "UIntP", slcs, "UIntP", slce, "Ptr")
		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 := RegExReplace((mpos > pmax ? SubStr(Hays, slcs) : SubStr(Hays, slcs, mpos - slcs)), "^\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
		{	ControlGet, sPat, Selected,, Edit2, A
			r := StrLen(sPat) ? (regCase ? "i)" : "") (regWhole ? "\b" : "") escrx(sPat) (regWhole ? "\b" : "") : ""
		}
		If StrLen(r)
		{	If SubStr(r, 0) = "|"
				r := SubStr(r, 1, StrLen(r)-1)
			ControlGettext, Hays, Edit2, A
			If RegExMatch(Hays, r)
			{	StartHere:
				Gosub, Compute_cp
				If RegExMatch(A_ThisHotkey, "WheelUp|\+f")
				{	Gosub, ReverseWay
					mpos -= 1
				}Else mpos := RegExMatch(Hays, r, nm, cp) - 1
				If mpos >= 0
				{	cp := mpos + StrLen(nm)
					SendMessage, 0xB1, mpos, cp,, ahk_id %hEdit2%	;EM_SETSEL
					SendMessage, 0xB7, 0, 0,, ahk_id %hEdit2%	;EM_SCROLLCARET
				}Else If (mpos < 0) and not RegExMatch(A_ThisHotkey, "WheelUp|\+f")
				{	SendMessage, 0xB1, 0, 0,, ahk_id %hEdit2%	;EM_SETSEL	;Send, ^{Home}
					Goto, StartHere
				}
			}Else MsgBox, Target Not Found
		}
		Return
		MakerFnm:
			sPat := tw4sh(sPat), r := (regCase ? "i" : "") "m`a)"
			Loop, Parse, sPat, % A_Space
				If SubStr(A_LoopField, 1, 1) != "-"
					r .= "(" (regWhole ? "\b" : "") A_LoopField (regWhole ? "\b" : "") ")|"
			Return
		Compute_cp:
			DllCall("User32\SendMessage", "Ptr", cid, "UInt", 0x00B0, "UIntP", slcs, "UIntP", slce, "Ptr"), slcs++, slce++
			cp := RegExMatch(A_ThisHotkey, "WheelUp|\+f") ? 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
#IfWinActive, The Text Filter
	F1::
		ControlGetText, Hays, Edit2, A
		If SubStr(Hays, 1, 17) = "H e l p _ P a g e"
			ControlSetText, Edit2, %B4Help%, A
		Else
		{	B4Help := Hays
			ControlSetText, Edit2, %HelpPage%, A
		}
		Return
	~Wheelup::
	~Wheeldown::
		MouseGetPos,,,, moc
		If not StrLen(moc) or not InStr("Edit1|Edit2", moc)
		{	ControlFocus, Edit2, A
			Goto, NextMatch
		}Else Return
	!a::
		ControlGetText, Hays, Edit2, A
		If SubStr(Hays, 1, 19) = "Assorted Matches:`t("
		{	If SubStr(Hays, 20, 12) = "Alphabetical"
				ControlSetText, Edit2, %AssHays%, A
			Else
				ControlSetText, Edit2, %AAssHays%, A
			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
		If SubStr(r, 0) = "|"
			r := SubStr(r, 1, StrLen(r)-1)
		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 := AssHays := "", umNoSort := []
			For k, v in UniqMatches
				UniqHays .= rn StrRepeat(A_Space, 3) A_Index ".`t" k, AssHays .= rn A_Index ". " AssHays%v%, umNoSort[v] ? umNoSort[v].Insert(k) : umNoSort[v] := k
			AAssHays := "Assorted Matches:`t(Alphabetical Order)" UniqHays rn StrRepeat("-", 50) AssHays
			UniqHays := AssHays := ""
			For k, v in umNoSort
				UniqHays .= rn StrRepeat(A_Space, 3) k ".`t" v, AssHays .= rn A_Index ". " AssHays%k%
			AssHays := "Assorted Matches:`t(Chronological Order)" UniqHays rn StrRepeat("-", 50) AssHays
			ControlSetText, Edit2, %AssHays%, A
		}
		Progress, Off
		Return
#IfWinActive
User avatar
rommmcek
Posts: 1470
Joined: 15 Aug 2014, 15:18

Re: Text Filter

21 Mar 2021, 14:01

Uploaded Text Filter Sci 1.1. Updated Pic. Tested on Win10 x64 120 dpi (125%). Made blind attempt (no tests) for other Dpi-s. App has still many quirks, but there will be no updates soon. Despite that, bug reports are welcome. Instructions: Read comments in the script after ::. My favorite: ^+o.
Edit: Due to serious bug fresh upload!
bye!
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

23 Mar 2021, 00:33

rommmcek wrote:
21 Mar 2021, 14:01
Uploaded Text Filter Sci 1.1. Updated Pic. Tested on Win10 x64 120 dpi (125%). Made blind attempt (no tests) for other Dpi-s. App has still many quirks, but there will be no updates soon. Despite that, bug reports are welcome. Instructions: Read comments in the script after ::. My favorite: ^+o.
Edit: Due to serious bug fresh upload!
bye!
@rommmcek

:bravo: THANK YOU.

i gained one more reason to learn scintilla, even though it still looks pretty hard.

i noticed that your zip file didn't include scilexer32aa.dll, as i guess scilexer-x86.dll which i happened to have one may do the job, i then renamed it and it seemed working fine.

will see if i can apply scintilla later on.

my first impression though, the response time is quite noticeably slower now. (it may be only for me, as i'm using a very low-graded pc.)

chao!
User avatar
rommmcek
Posts: 1470
Joined: 15 Aug 2014, 15:18

Re: Text Filter

23 Mar 2021, 04:22

To start Scintilla maybe this thread/script will be helpful.

Happy dominical coding!
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

01 Apr 2021, 01:51

added a new feature, the matches highlighting. this is implemented through richedit, not scintilla. it works quite good to me so far.
it looks like this
since it's in rich text format now, the help page can be colored and with better paragraph alignments. so, i made a rtf file for it, attached, which should be placed at the same folder of this script. it can still run fine without it though.
it looks like this

rtf help_page -

[The extension rtf has been deactivated and can no longer be displayed.]

Code: Select all

Version = Planet-Seven-D
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 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
\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
Gui, +Delimiter`n
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, Edit, ReadOnly x10 h%h% w%w% %begWwrap% VScroll +hwndhEdit2
Gui, Add, Custom, ClassRICHEDIT50W x10 h%h% w%w% %begWwrap% +VScroll +hwndHRE +0x0004	;ES_MULTILINE
Gui, Show,, The Text Filter,, The Text Filter (Version: %Version%) - Level %n%   ( Base %blc% Lines )
RE := GetTomDoc(HRE)
;ControlGet, cid, hwnd,, Edit2, A
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 GuiControl,, RICHEDIT50W1, % Haystack%n%	;GuiControl,, Edit2, % Haystack%n%
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 SubStr(r, 0) = "|"
		r := SubStr(r, 1, StrLen(r)-1)
	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:
StrReplace(Haystack%n%, "`n",, blc), blc += StrLen(Haystack%n%) and SubStr(Haystack%n%, 0) != "`n" ? 1 : 0
WinSetTitle, The Text Filter,, The Text Filter (Version: %Version%) - Level %n%   ( Base %blc% Lines )
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}	;SendMessage, 0xB1, cp:=StrLen(Needle%n%), cp,, ahk_id %hEdit1%	;EM_SETSEL
	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
			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	;SendMessage, 0xB1, 0, StrLen(Needle%pn%),, ahk_id %hEdit1%	;EM_SETSEL
			}
		}
		CommitLevel = 0
		Sleep, 500
		Progress, Off
	}Else Haystack%n% := theHaystack
	Goto, RenewGui
	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
	{	Gosub, RenewGui
		ControlSetText, Edit1, % Needle%n%
		Send, ^a	;SendMessage, 0xB1, 0, StrLen(Needle%n%),, ahk_id %hEdit1%	;EM_SETSEL
		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
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	;GuiControlGet, 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 = "\")
				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
	;msgbox % t "`n`n" r
	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	;ControlFocus, Edit2, A
#If, WinActive("The Text Filter") and FocusedControl() = "RICHEDIT50W1"	;"Edit2"
	Tab::ControlFocus, Edit1, A
	~Space::PgDn
	~+Space::PgUp
	~s::Home
	~e::End
	~t::^Home
	~b::^End
	`::
	~Enter::
		ControlGetText, Hays, RICHEDIT50W1, A	;ControlGetText, Hays, Edit2, A
		StringReplace, Hays, Hays, `r`n, `n, All	;for RichEdit
		slcs := RE.Selection.Start, slce := RE.Selection.End	;DllCall("User32\SendMessage", "Ptr", cid, "UInt", 0x00B0, "UIntP", slcs, "UIntP", slce, "Ptr")
		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 := RegExReplace((mpos > pmax ? SubStr(Hays, slcs) : SubStr(Hays, slcs, mpos - slcs)), "^\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	;ControlGet, sPat, Selected,, Edit2, A
			r := StrLen(sPat) ? (regCase ? "i)" : "") (regWhole ? "\b" : "") escrx(sPat) (regWhole ? "\b" : "") : ""
		}
		If StrLen(r)
		{	If SubStr(r, 0) = "|"
				r := SubStr(r, 1, StrLen(r)-1)
			ControlGettext, Hays, RICHEDIT50W1, A	;ControlGettext, Hays, Edit2, A
			StringReplace, Hays, Hays, `r`n, `n, All	;for RichEdit
			If RegExMatch(Hays, r)
			{	StartHere:
				Gosub, Compute_cp
				If RegExMatch(A_ThisHotkey, "WheelUp|\+f")
				{	Gosub, ReverseWay
					mpos -= 1
				}Else mpos := RegExMatch(Hays, r, nm, cp) - 1
				If mpos >= 0
				{	cp := mpos + StrLen(nm)
					RE.Range(mpos, cp).Select
					;SendMessage, 0xB1, mpos, cp,, ahk_id %hEdit2%	;EM_SETSEL
					;SendMessage, 0xB7, 0, 0,, ahk_id %hEdit2%	;EM_SCROLLCARET
				}Else If (mpos < 0) and not RegExMatch(A_ThisHotkey, "WheelUp|\+f")
				{	RE.Range(0, 0).Select	;SendMessage, 0xB1, 0, 0,, ahk_id %hEdit2%	;EM_SETSEL	;Send, ^{Home}
					Goto, StartHere
				}
			}Else MsgBox, Target Not Found
		}
		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)
				r := (regCase ? "i" : "") "m`a)" r
			Return
		Compute_cp:
			slcs := RE.Selection.Start, slce := RE.Selection.End, slcs++, slce++	;DllCall("User32\SendMessage", "Ptr", cid, "UInt", 0x00B0, "UIntP", slcs, "UIntP", slce, "Ptr"), slcs++, slce++
			cp := RegExMatch(A_ThisHotkey, "WheelUp|\+f") ? 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
#IfWinActive, The Text Filter
	F1::
		ControlGetText, Hays, RICHEDIT50W1, A	;ControlGetText, Hays, Edit2, 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	;ControlSetText, Edit2, %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)
			;ControlSetText, RICHEDIT50W1, %HelpPage%, A	;ControlSetText, Edit2, %HelpPage%, A
		}
		Return
	~Wheelup::
	~Wheeldown::
		MouseGetPos,,,, moc
		If not StrLen(moc) or not InStr("Edit1|RICHEDIT50W1", moc)	;InStr("Edit1|Edit2", moc)
		{	ControlFocus, RICHEDIT50W1, A	;ControlFocus, Edit2, A
			Goto, NextMatch
		}Else Return
	!a::
		fromAssortment := True
		ControlGetText, Hays, RICHEDIT50W1, A	;ControlGetText, Hays, Edit2, A
		If SubStr(Hays, 1, 19) = "Assorted Matches:`t("
		{	If SubStr(Hays, 20, 12) = "Alphabetical"
				ControlSetText, RICHEDIT50W1, %AssHays%, A	;ControlSetText, Edit2, %AssHays%, A
			Else
				ControlSetText, RICHEDIT50W1, %AAssHays%, A	;ControlSetText, Edit2, %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
		If SubStr(r, 0) = "|"
			r := SubStr(r, 1, StrLen(r)-1)
		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 := AssHays := "", umNoSort := []
			For k, v in UniqMatches
				UniqHays .= rn StrRepeat(A_Space, 3) A_Index ".`t" k, AssHays .= rn A_Index ". " AssHays%v%, umNoSort[v] ? umNoSort[v].Insert(k) : umNoSort[v] := k
			AAssHays := "Assorted Matches:`t(Alphabetical Order)" UniqHays rn StrRepeat("-", 50) AssHays
			UniqHays := AssHays := ""
			For k, v in umNoSort
				UniqHays .= rn StrRepeat(A_Space, 3) k ".`t" v, AssHays .= rn A_Index ". " AssHays%k%
			AssHays := "Assorted Matches:`t(Chronological Order)" UniqHays rn StrRepeat("-", 50) AssHays
			StringReplace, AssHays, AssHays, `r`n, `n, All	;for RichEdit
			StringReplace, AAssHays, AAssHays, `r`n, `n, All	;for RichEdit
			ControlSetText, RICHEDIT50W1, %AssHays%, A	;ControlSetText, Edit2, %AssHays%, A
			Gosub, RenewGui
		}
		Progress, Off
		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
#IfWinActive
that's it for now.
Last edited by SundayProgrammer on 08 Apr 2021, 03:36, edited 5 times in total.
ozzii
Posts: 481
Joined: 30 Oct 2013, 06:04

Re: Text Filter

01 Apr 2021, 03:25

Thank you @SundayProgrammer for this update.
Maybe just at the exist a delete of 'tfWork.rtf'
hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: Text Filter

01 Apr 2021, 08:55

SundayProgrammer wrote:
01 Apr 2021, 01:51
added a new feature, the matches highlighting. this is implemented through richedit, not scintilla. it works quite good to me so far.
it looks like this
since it's in rich text format now, the help page can be colored and with better paragraph alignments. so, i made a rtf file for it, attached, which should be placed at the same folder of this script. it can still run fine without it though.
it looks like this

rtf help_page - tfHelp.rtf

Code: Select all

SetWorkingDir %A_ScriptDir%
RE_Dll := DllCall("LoadLibrary", "Str", "Msftedit.dll", "Ptr")

HelpPage=
(
H e l p _ P a g e

F1 - to toggle this help page

Basic syntax by example:
	coexisting words/strings/patterns -unwanted
	for instance, d.*d -the and -oo \ws
	(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.)

Feature keys:
	Alt+a - to show assorted matches and toggle between chronological and alphabetical order.
	WheelDown - to find the next match (downward) from the caret position (given that, the mouse pointer is currently outside both text/edit boxes.)
	WheelUp - to find the next match (upward) from the caret position (given that, the mouse pointer is currently outside both text/edit boxes.)
	Alt+v - to paste clipboard content into the big text/edit box.
	Esc - to quit/exit/leave. same as closing the window.
	Tab - to change focus between the text/edit boxes (just try it.)

	these below only work when the big text/edit box is in focus:
		f - to find the next match (downward) from the caret position.
		Shift+f - to find the next match (upward) from the caret position.
		(note: "the line" refers to the current line, that's where the caret is on.)
		Enter - to copy the line.
		Space - to page down
		Shift+Space - to page up
		s - to move the caret to the beginning/start of the line.
		e - to move the caret to the end of the line.
		t - to move the caret to the top of the text.
		b - to move the caret to the bottom of the text.
		(side note: "caret" may be known as "cursor" to some people.)

	these below only work when the small text/edit box is in focus:
		Enter - to commit a level (which makes the current result becoming the base/source of the next level.) same as clicking the button Go.

	Backtick (``) - 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.

The improvised shorthand feature:
	this feature can be triggered through "\/" (a backslash followed by a slash), a "regex like" syntax style, which toggle it on or off. so, enter "\/tr" will bring you those lines contained "the road", "two roads", as well as "them really" out of the poem, whereas enter "oo \/a?a" or "\/a?a\/ oo" will return you these two lines "And looked down one as far as I could" and "Then took the other, as just as fair," in which, the "?" 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 "\/tr yw" or "\/yw tr" will show this line "Two roads diverged in a yellow wood," where the space is still a separator as always, even though the shorthand feature is toggled on.

	three more elements of the shorthand syntax:

	1, extra qualifier (within a single word);
	2, escape character (between words);
	3, a single character representing any "non-word" one (between words).

	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, "sameto" can match "some are more equal than others") yet sometimes we may want to further narrow down the result. if "slh" brings you both "she likes him" and "she loves him", an extra qualifier to pinpoint one may be useful. it could simply be a "v" for this example hence "slv/h" (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 "she loves him" only. likewise, you may sometimes want to tell the system exactly what the word should be ending with, for instance, if "ioy" brings you both "i owe you" and "i own you", "ioe//y" (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 "i owe you" only. and no matter how rare (and impractical) it may be, you may have multiple of these qualifiers for a word, such as, "cm/c/e//d//" can match the word "complicated" whereas "td/i/u//s//" can match the word "tedious".

	a backslash "\" 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 double backslash "\\" 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 consecutive "word" characters that need to be escaped, enclose them in the \Q \E pair, similar idea as in regex. one thing to note though, escaped character may affect the word immediately after it, for instance, "z\xy" may bring you "zebra xylophone" rather than "zombie x yesterday".

	an apostrophe "'" 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.

End of this help page.
)
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 := fromButtonRefresh := 0
theGui:
Gui, tf:New
Gui, +Delimiter`n
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, Edit, ReadOnly x10 h%h% w%w% %begWwrap% VScroll +hwndhEdit2
Gui, Add, Custom, ClassRICHEDIT50W x10 h%h% w%w% %begWwrap% +VScroll +hwndHRE +0x0004	;ES_MULTILINE
Gui, Show,, The Text Filter,, The Text Filter - Level %n%   ( Base %blc% Lines )
RE := GetTomDoc(HRE)
;ControlGet, cid, hwnd,, Edit2, A
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
ControlGetText, Hays, RICHEDIT50W1, A
If not (fromButtonRefresh and SubStr(Hays, 1, 19) = "Assorted Matches:`t(")
	GuiControl,, RICHEDIT50W1, % Haystack%n%	;GuiControl,, Edit2, % Haystack%n%
fromButtonRefresh := False
ControlGetText, Hays, RICHEDIT50W1, A
StringReplace, Hays, Hays, `r`n, `n, All	;for RichEdit
ControlGetText, sPat, Edit1, A
If StrLen(sPat)
{	Gosub, MakerFnm
	If SubStr(r, 0) = "|"
		r := SubStr(r, 1, StrLen(r)-1)
	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)
			mlen := StrLen(nm), RE.Range(mpos - 1, mpos + mlen - 1).Font.BackColor := 0x00FFFF, npos := mpos
		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:
StrReplace(Haystack%n%, "`n",, blc), blc += StrLen(Haystack%n%) and SubStr(Haystack%n%, 0) != "`n" ? 1 : 0
WinSetTitle, The Text Filter,, The Text Filter - Level %n%   ( Base %blc% Lines )
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:
	regWwrap := Wwrap()
	Needle%n% := Fpat()
	Gui, tf:Destroy
	Gosub, theGui
	ControlSetText, Edit1, % Needle%n%, A
	Send, {End}	;SendMessage, 0xB1, cp:=StrLen(Needle%n%), cp,, ahk_id %hEdit1%	;EM_SETSEL
	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
			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	;SendMessage, 0xB1, 0, StrLen(Needle%pn%),, ahk_id %hEdit1%	;EM_SETSEL
			}
		}
		CommitLevel = 0
		Sleep, 500
		Progress, Off
	}Else Haystack%n% := theHaystack
	Goto, RenewGui
	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:
	fromButtonRefresh := True
	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
	{	Gosub, RenewGui
		ControlSetText, Edit1, % Needle%n%
		Send, ^a	;SendMessage, 0xB1, 0, StrLen(Needle%n%),, ahk_id %hEdit1%	;EM_SETSEL
		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
tfGuiEscape:
tfGuiClose:
	ExitApp

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	;GuiControlGet, 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
			{	r .= xx ? pb "*?" xx st : 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
				r .= xx ? pb "*?" xx st : st, xx := A_Space, rr := 0
			Else If (c = "'")
				flush(r, xx, pb, yb), r .= "\W"
			Else If (c = "\")
				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)
					flush(r, xx, pb, yb), r .= c
				Else If RegExMatch(cc, "\w")
					skip_w := skip_w ? skip_w : True, flush(r, xx, pb, yb)
				Else skip_c++, flush(r, xx, pb, yb), r .= c cc
			Else xx .= c
			mpos++
			If (mpos > StrLen(t))	;exceeded
			{	If rr
					r .= xx ? pb "*?" xx st : st, rr := xx := ""
				Break
			}
		}
	}Else r := t
	;msgbox % t "`n`n" r
	Return r
}
flush(ByRef r, ByRef xx, pb, ByRef yb) {
	r .= xx ? pb "*?" xx pb "*?" : (yb ? (pb "+?", 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	;ControlFocus, Edit2, A
#If, WinActive("The Text Filter") and FocusedControl() = "RICHEDIT50W1"	;"Edit2"
	Tab::ControlFocus, Edit1, A
	~Space::PgDn
	~+Space::PgUp
	~s::Home
	~e::End
	~t::^Home
	~b::^End
	`::
	~Enter::
		ControlGetText, Hays, RICHEDIT50W1, A	;ControlGetText, Hays, Edit2, A
		slcs := RE.Selection.Start, slce := RE.Selection.End	;DllCall("User32\SendMessage", "Ptr", cid, "UInt", 0x00B0, "UIntP", slcs, "UIntP", slce, "Ptr")
		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 := RegExReplace((mpos > pmax ? SubStr(Hays, slcs) : SubStr(Hays, slcs, mpos - slcs)), "^\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	;ControlGet, sPat, Selected,, Edit2, A
			r := StrLen(sPat) ? (regCase ? "i)" : "") (regWhole ? "\b" : "") escrx(sPat) (regWhole ? "\b" : "") : ""
		}
		If StrLen(r)
		{	If SubStr(r, 0) = "|"
				r := SubStr(r, 1, StrLen(r)-1)
			ControlGettext, Hays, RICHEDIT50W1, A	;ControlGettext, Hays, Edit2, A
			StringReplace, Hays, Hays, `r`n, `n, All	;for RichEdit
			If RegExMatch(Hays, r)
			{	StartHere:
				Gosub, Compute_cp
				If RegExMatch(A_ThisHotkey, "WheelUp|\+f")
				{	Gosub, ReverseWay
					mpos -= 1
				}Else mpos := RegExMatch(Hays, r, nm, cp) - 1
				If mpos >= 0
				{	cp := mpos + StrLen(nm)
					RE.Range(mpos, cp).Select
					;SendMessage, 0xB1, mpos, cp,, ahk_id %hEdit2%	;EM_SETSEL
					;SendMessage, 0xB7, 0, 0,, ahk_id %hEdit2%	;EM_SCROLLCARET
				}Else If (mpos < 0) and not RegExMatch(A_ThisHotkey, "WheelUp|\+f")
				{	RE.Range(0, 0).Select	;SendMessage, 0xB1, 0, 0,, ahk_id %hEdit2%	;EM_SETSEL	;Send, ^{Home}
					Goto, StartHere
				}
			}Else MsgBox, Target Not Found
		}
		Return
		MakerFnm:
			sPat := tw4sh(sPat), r := (regCase ? "i" : "") "m`a)"
			Loop, Parse, sPat, % A_Space
				If SubStr(A_LoopField, 1, 1) != "-"
					r .= "(" (regWhole ? "\b" : "") A_LoopField (regWhole ? "\b" : "") ")|"
			Return
		Compute_cp:
			slcs := RE.Selection.Start, slce := RE.Selection.End, slcs++, slce++	;DllCall("User32\SendMessage", "Ptr", cid, "UInt", 0x00B0, "UIntP", slcs, "UIntP", slce, "Ptr"), slcs++, slce++
			cp := RegExMatch(A_ThisHotkey, "WheelUp|\+f") ? 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
#IfWinActive, The Text Filter
	F1::
		ControlGetText, Hays, RICHEDIT50W1, A	;ControlGetText, Hays, Edit2, A
		If SubStr(Hays, 1, 17) = "H e l p _ P a g e"
			ControlSetText, RICHEDIT50W1, %B4Help%, A	;ControlSetText, Edit2, %B4Help%, A
		Else
		{	B4Help := Hays
			IfExist, tfHelp.rtf
			{	RE.New
				If ComObjCreate("WScript.Shell").Exec("cmd.exe /c copy/b/v/y tfHelp.rtf tfWork.rtf").StdOut.ReadAll()
					RE.Open("tfWork.rtf", 0x01, 0)
			}
			Else
				ControlSetText, RICHEDIT50W1, %HelpPage%, A	;ControlSetText, Edit2, %HelpPage%, A
		}
		Return
	~Wheelup::
	~Wheeldown::
		MouseGetPos,,,, moc
		If not StrLen(moc) or not InStr("Edit1|RICHEDIT50W1", moc)	;InStr("Edit1|Edit2", moc)
		{	ControlFocus, RICHEDIT50W1, A	;ControlFocus, Edit2, A
			Goto, NextMatch
		}Else Return
	!a::
		ControlGetText, Hays, RICHEDIT50W1, A	;ControlGetText, Hays, Edit2, A
		If SubStr(Hays, 1, 19) = "Assorted Matches:`t("
		{	If SubStr(Hays, 20, 12) = "Alphabetical"
				ControlSetText, RICHEDIT50W1, %AssHays%, A	;ControlSetText, Edit2, %AssHays%, A
			Else
				ControlSetText, RICHEDIT50W1, %AAssHays%, A	;ControlSetText, Edit2, %AAssHays%, A
			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
		If SubStr(r, 0) = "|"
			r := SubStr(r, 1, StrLen(r)-1)
		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 := AssHays := "", umNoSort := []
			For k, v in UniqMatches
				UniqHays .= rn StrRepeat(A_Space, 3) A_Index ".`t" k, AssHays .= rn A_Index ". " AssHays%v%, umNoSort[v] ? umNoSort[v].Insert(k) : umNoSort[v] := k
			AAssHays := "Assorted Matches:`t(Alphabetical Order)" UniqHays rn StrRepeat("-", 50) AssHays
			UniqHays := AssHays := ""
			For k, v in umNoSort
				UniqHays .= rn StrRepeat(A_Space, 3) k ".`t" v, AssHays .= rn A_Index ". " AssHays%k%
			AssHays := "Assorted Matches:`t(Chronological Order)" UniqHays rn StrRepeat("-", 50) AssHays
			StringReplace, AssHays, AssHays, `r`n, `n, All	;for RichEdit
			StringReplace, AAssHays, AAssHays, `r`n, `n, All	;for RichEdit
			ControlSetText, RICHEDIT50W1, %AssHays%, A	;ControlSetText, Edit2, %AssHays%, A
		}
		Progress, Off
		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
#IfWinActive
that's it for now.
Thank you, this good thing. I've had a similar work with RTF. I struggled with scintilla when it was too slow for large documents.
User avatar
rommmcek
Posts: 1470
Joined: 15 Aug 2014, 15:18

Re: Text Filter

01 Apr 2021, 13:15

Scintilla is much faster then RichEdit, but for very large files still to slow moreover, even compiled/optimized binary (SciTE4Autohotkey) is not fast enough in such cases. That's why I'm marking only the visible text.
@SundayProgrammer: If you can do the same, RichEdit would be real time too!
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

01 Apr 2021, 14:52

rommmcek wrote:
01 Apr 2021, 13:15
Scintilla is much faster then RichEdit, but for very large files still to slow moreover, even compiled/optimized binary (SciTE4Autohotkey) is not fast enough in such cases. That's why I'm marking only the visible text.
@SundayProgrammer: If you can do the same, RichEdit would be real time too!
that's a good idea. but my lazy take would be skipping it when the search result is still too big. coz i think people will further narrow it down, as it probably still far from pinpointing what they're looking for. let's say make it at 100 kilobytes. under that, do highlighting. over that, skip highlighting.

ozzii wrote:
01 Apr 2021, 03:25
Thank you @SundayProgrammer for this update.
Maybe just at the exist a delete of 'tfWork.rtf'
okay.

hasantr wrote:
01 Apr 2021, 08:55
Thank you, this good thing. I've had a similar work with RTF. I struggled with scintilla when it was too slow for large documents.
i ended up taking richedit not because of its speed (as i didn't know which one is actually faster), but its size, as no extra library is needed. and it's not too hard for me to understand how it works. and its speed is acceptable, to me at least.
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

02 Apr 2021, 04:00

SundayProgrammer wrote:
01 Apr 2021, 14:52
rommmcek wrote:
01 Apr 2021, 13:15
Scintilla is much faster then RichEdit, but for very large files still to slow moreover, even compiled/optimized binary (SciTE4Autohotkey) is not fast enough in such cases. That's why I'm marking only the visible text.
@SundayProgrammer: If you can do the same, RichEdit would be real time too!
that's a good idea. but my lazy take would be skipping it when the search result is still too big. coz i think people will further narrow it down, as it probably still far from pinpointing what they're looking for. let's say make it at 100 kilobytes. under that, do highlighting. over that, skip highlighting.

ozzii wrote:
01 Apr 2021, 03:25
Thank you @SundayProgrammer for this update.
Maybe just at the exist a delete of 'tfWork.rtf'
okay.
as an afterthought, i've changed the lazy plan a little bit. now, the default behavior is to highlight no more than a thousand matches (which can improve the response time when too many matches are found.) but it can be overrode 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 one second or longer. therefore, highlighting will cover the whole thing even if the total number of matches is over one thousand. this long-press-to-override is not a toggle, it's just a one time thing, so do it every time you want more than the default.

tfWork.rtf will now be deleted at guiclose if it exists.

the script is updated.
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

03 Apr 2021, 01:12

made some adjustments.

1, highlighting after word wrap toggling wasn't done automatically, and now got fixed.

2. highlighting after alt+a wasn't done automatically, and now got fixed.

3. indentation for line numbering got improvement.

the script is updated.
ozzii
Posts: 481
Joined: 30 Oct 2013, 06:04

Re: Text Filter

03 Apr 2021, 03:44

Thank you for the updates.
Some remarks if I may.
- In the help, I don't see anything about the long press for example. I know that maintaining a help file is not your strong side (like you said :P )
- Is there any way/possibility to be informed when a new help file is available within the app? Because now I download the file every time that a new version of the script is available
- Is it possible to have a scroll bar in the dialog? Because I have a #MaxHotkeysPerInterval error
- An idea for improving the help so that you don't need to use an external file. If it's possible to integrate it into a dialogue; the use of markdown syntax. But I don't know if AH can use the markdown syntax
- Maybe an ini file so that we can change the default check of the options without having to change the main script. Maybe also in this file having the possibility to change the font of the text box

Sorry for all of these remarks :headwall:
I still LOVE your script :bravo: :dance: :superhappy:
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

03 Apr 2021, 06:20

ozzii wrote:
03 Apr 2021, 03:44
Thank you for the updates.
Some remarks if I may.
- In the help, I don't see anything about the long press for example. I know that maintaining a help file is not your strong side (like you said :P )
- Is there any way/possibility to be informed when a new help file is available within the app? Because now I download the file every time that a new version of the script is available
- Is it possible to have a scroll bar in the dialog? Because I have a #MaxHotkeysPerInterval error
- An idea for improving the help so that you don't need to use an external file. If it's possible to integrate it into a dialogue; the use of markdown syntax. But I don't know if AH can use the markdown syntax
- Maybe an ini file so that we can change the default check of the options without having to change the main script. Maybe also in this file having the possibility to change the font of the text box

Sorry for all of these remarks :headwall:
I still LOVE your script :bravo: :dance: :superhappy:
thank you for your input. they are all valuable indeed. like, i can't think of any suggestions that you made before and not being implemented. that should reflect their weight, i suppose.

i can do coding or debugging for many hours continuously, but every time i do documentation i got tired very quickly. i think it's because it's not interesting nor challenging by nature, even though i completely agree that it's very vital to do so.

i can feel your frustration from the workload in updating the script at your end as a user. i understand that it's quite inconvenient. i would probably feel the same in putting on your shoes.

will keep all your suggestions in my list and have them done the soonest possible. however, i keep on having new ideas these days which made me busy enough unfortunately. (or maybe "fortunately" in other perspective.) anyway, just keep going.

for the "...dialog...error" problem, could you further describe it? i don't think i got what's it all about yet.

thank you again, and wish you a happy easter holiday!
ozzii
Posts: 481
Joined: 30 Oct 2013, 06:04

Re: Text Filter

03 Apr 2021, 11:05

SundayProgrammer wrote: but every time i do documentation i got tired very quickly
I sympathize.
But remembering all the possibilities/shortcuts of your script is a real PITA because you've done so much work and your script has so many features.
I think that a simple text with enumeration will work. Like a copy/past of your text when you add a feature and put it in the post.
In that way, we can't miss a shortcut.
But it's your call, like always, because you are maintaining the script.
SundayProgrammer wrote: will keep all your suggestions in my list and have them done the soonest possible. however, i keep on having new ideas these days which made me busy enough unfortunately. (or maybe "fortunately" in other perspective.) anyway, just keep going.
I will say fortunately for you :roll: and thank you for keeping my suggestions in mind (even if all/some/none are/not implemented).
SundayProgrammer wrote: for the "...dialog...error" problem, could you further describe it? i don't think i got what's it all about yet.
Well, maybe another script is interfering with yours. I will do some more test.
SundayProgrammer wrote: thank you again, and wish you a happy easter holiday!
No, thank you... And happy Easter holiday to you too.
SundayProgrammer
Posts: 143
Joined: 25 Dec 2020, 12:26

Re: Text Filter

06 Apr 2021, 11:51

the script is updated.

1, from now on, the tfHelp.rtf file is no longer needed. the script can automatically produce one whenever needed.

2, i have summarized the releases from the old posts and made a brief change log and gave each major update a version name. and put it in the original post → viewtopic.php?p=374710&sid=149731e72968a7efb4ca0bffb9b63757#p374710 ← click this link

3, there is a version name next to the window title from now on.

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

Re: Text Filter

07 Apr 2021, 07:14

SundayProgrammer wrote: 1, from now on, the tfHelp.rtf file is no longer needed. the script can automatically produce one whenever needed.
:bravo:
SundayProgrammer wrote: 2, i have summarized the releases from the old posts and made a brief change log and gave each major update a version name. and put it in the original post → viewtopic.php?p=374710&sid=149731e72968a7efb4ca0bffb9b63757#p374710 ← click this link
:dance:

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: KruschenZ and 109 guests