does string look like a hotkey

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

does string look like a hotkey

21 Oct 2018, 11:38

For conversion, I need a function that can report whether something looks like a hotkey. I'm asking in case anyone has any suggestions.
E.g. LooksLikeHotkey("+^#!q")
E.g. StrIsTypeHotkey("RAlt & Enter")

Code: Select all

;before/after:
q::MsgBox, % "hello"
q::MsgBox("hello")
Any little checks that can rule out something being a hotkey could be useful. E.g. is it true that a hotkey can contain 2 streams of whitespace maximum e.g. 'RAlt & Enter', or that it can contain % once maximum?
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: does string look like a hotkey

22 Oct 2018, 16:38

Code: Select all

#NoEnv
#SingleInstance Force
SetBatchLines -1

fn := Func("isLikeHotkey")
eq := Func("assert").Bind(true, fn)
neq := Func("assert").Bind(false, fn)

valid := "Testing valid hotkeys`n`n"
valid .= eq.Call("Up Up")
valid .= eq.Call("Up")
valid .= eq.Call("``") ; hotkey is meant to be single `::
valid .= eq.Call("`; Up")
valid .= eq.Call(":")
valid .= eq.Call("  	 &   & & ")
valid .= eq.Call("  	 &   & k")
valid .= eq.Call("h  	  & 		 j Up")
valid .= eq.Call("~*>$>>+!^#<g Up")
valid .= eq.Call("vk24 & vk23")
valid .= eq.Call("sc123")
valid .= eq.Call("z")
MsgBox % valid

invalid := "Testing invalid hotkeys`n`n"
invalid .= neq.Call("") ; blank hotkey, ie ::
invalid .= neq.Call("    	*  	g &   	 h Up")
invalid .= neq.Call("not_a_hotkey")
invalid .= neq.Call("g Up & g Up")
invalid .= neq.Call("vk12sc123")
invalid .= neq.Call("vk123456")
MsgBox % invalid

Clipboard := valid invalid
ExitApp

Esc::ExitApp

isLikeHotkey(str) {
	static VALID_HOTKEY := "(\b(LButton|RButton|MButton|XButton1|XButton2|WheelDown|WheelUp|WheelLeft|WheelRight|CapsLock|Space|Tab|Enter|Return|Escape|Esc|Backspace|BS|ScrollLock|Delete|Del|Insert|Ins|Home|End|PgUp|PgDn|Up|Down|Left|Right|Numpad0|NumpadIns|Numpad1|NumpadEnd|Numpad2|NumpadDown|Numpad3|NumpadPgDn|Numpad4|NumpadLeft|Numpad5|NumpadClear|Numpad6|NumpadRight|Numpad7|NumpadHome|Numpad8|NumpadUp|Numpad9|NumpadPgUp|NumpadDot|NumpadDel|NumLock|NumpadDiv|NumpadMult|NumpadAdd|NumpadSub|NumpadEnter|F1|F2|F3|F4|F5|F6|F7|F8|F9|F10|F11|F12|F13|F14|F15|F16|F17|F18|F19|F20|F21|F22|F23|F24|LWin|RWin|Control|Ctrl|Alt|Shift|LControl|LCtrl|RControl|RCtrl|LShift|RShift|LAlt|RAlt|Browser_Back|Browser_Forward|Browser_Refresh|Browser_Stop|Browser_Search|Browser_Favorites|Browser_Home|Volume_Mute|Volume_Down|Volume_Up|Media_Next|Media_Prev|Media_Stop|Media_Play_Pause|Launch_Mail|Launch_Media|Launch_App1|Launch_App2|AppsKey|PrintScreen|CtrlBreak|Pause|Break|Help|Sleep|vk[a-fA-F0-9]{2}|sc[a-fA-F0-9]{3})\b|`;|([^\s](?!\w)))"
		 , OPTIONAL_PASSTHROUGH := "(~?)"
		 , WHITESPACE := "(\s*)"
		 , AMPERSAND_JOIN := "(&)"
		 , OPTIONAL_UP := "(Up)?"
		 , BOL := "^"
		 , SINGLE_HOTKEY_MODIFIERS := "([~*$+^!#<>]*)"

	if (str = "")
		return false

	Loop ; count how many '&' in hotkey
	{
		Pos := InStr(str, "&", , Pos + 1)
		numAmpersands := A_Index - 1
	} Until !Pos

	if numAmpersands in 0 ; single hotkey check
		return !!RegExMatch(str, BOL
							   . WHITESPACE
							   . SINGLE_HOTKEY_MODIFIERS
							   . VALID_HOTKEY
							   . WHITESPACE
							   . OPTIONAL_UP)

	else if numAmpersands in 1,2,3 ; multihotkey check
		return !!RegExMatch(str, BOL
							   . WHITESPACE
							   . OPTIONAL_PASSTHROUGH
							   . VALID_HOTKEY
							   . WHITESPACE
							   . AMPERSAND_JOIN
							   . WHITESPACE
							   . OPTIONAL_PASSTHROUGH
							   . VALID_HOTKEY
							   . WHITESPACE
							   . OPTIONAL_UP)

	else ; invalid hotkey, too many & & & & ...
		return false

	throw new Exception(Format("Error: {}(""{}"") wasnt caught by any of the checks", A_ThisFunc, str))
}

assert(condition, fn, arg) {
	static MESSAGE := "<{}>: {}(""{}"") == '{}'`n"
	return Format(MESSAGE
			, fn.Call(arg) == condition ? "PASS" : "FAIL"
			, fn.Name
			, arg
			, condition ? "true" : "false")
}
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: does string look like a hotkey

22 Oct 2018, 18:09

- Very interesting, thanks very much swagfag.
- Nice also how you've used the 'assert' function.
- Btw did you find some example code in the source code or in an editor perhaps?
- Btw you could count the ampersands like this, right?
StrReplace(str, "&",, numAmpersands)

- One thing I was curious about was whether AutoHotkey and the function itself could handle 'a & &' or '& & a'. (It appears that both AHK and the function view those as acceptable hotkeys.)
- It appears that & by itself fails in the function, although it is valid, although the function could easily check for that.

Code: Select all

;fails (but the hotkey is valid):
invalid .= neq.Call("&")

;fails (as expected):
invalid .= neq.Call("a up & b up")

;succeed (and are valid):
valid .= eq.Call("LCtrl & &")
valid .= eq.Call("& & &")
valid .= eq.Call("LCtrl & LCtrl")
valid .= eq.Call("Ctrl & Ctrl")
valid .= eq.Call("a & a")
valid .= eq.Call("a & b")
valid .= eq.Call("a & b up")
valid .= eq.Call("a & &")
valid .= eq.Call("& & a")

Code: Select all

;&::SoundBeep
;LCtrl & &::SoundBeep ;works if press Ctrl+7 (without Shift)
;& & &::SoundBeep
;LCtrl & LCtrl::SoundBeep ;doesn't work (but doesn't trigger error either)
;Ctrl & Ctrl::SoundBeep ;doesn't work if press LCtrl by itself, does work if use LCtrl and RCtrl
;a & a::SoundBeep ;doesn't work (but doesn't trigger error either)
;a & b::SoundBeep
;a & b up::SoundBeep
;a & &::SoundBeep ;can work, a bit unreliable
;& & a::SoundBeep ;can work, a bit unreliable

;a up & b up::SoundBeep ;error message: 'Invalid hotkey'
- I'd mentioned this 'identify hotkey' issue twice before:
AHK v1 to AHK v2 converter - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 37&t=36754
parsing AutoHotkey scripts - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=40471
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: does string look like a hotkey

22 Oct 2018, 19:46

i wasnt able to recall which string function returned the count of matched instances and i mustve overlooked strreplace in the docs, hence the cruddy loop
i didnt think of &::. the problem was the ampersand was being evaluated as the joining char for 2 blank hotkey, resulting in the regex for multihotkeys being applied
fixed and refactored now, + ur tests:

Code: Select all

#NoEnv
#SingleInstance Force
SetBatchLines -1

fn := Func("isLikeHotkey")
eq := Func("assert").Bind(true, fn)
neq := Func("assert").Bind(false, fn)

valid := "Testing valid hotkeys`n`n"
valid .= eq.Call("Up Up")
valid .= eq.Call("Up")
valid .= eq.Call("``") ; hotkey is meant to be single `::
valid .= eq.Call("`; Up")
valid .= eq.Call(":")
valid .= eq.Call("  	 &   & & ")
valid .= eq.Call("  	 &   & k")
valid .= eq.Call("h  	  & 		 j Up")
valid .= eq.Call("~*>$>>+!^#<g Up")
valid .= eq.Call("vk24 & vk23")
valid .= eq.Call("sc123")
valid .= eq.Call("z")
valid .= eq.Call("LCtrl & &")
valid .= eq.Call("& & &")
valid .= eq.Call("LCtrl & LCtrl")
valid .= eq.Call("Ctrl & Ctrl")
valid .= eq.Call("a & a")
valid .= eq.Call("a & b")
valid .= eq.Call("a & b up")
valid .= eq.Call("a & &")
valid .= eq.Call("& & a")
valid .= eq.Call("&")
MsgBox % valid

invalid := "Testing invalid hotkeys`n`n"
invalid .= neq.Call("") ; blank hotkey, ie ::
invalid .= neq.Call("    	*  	g &   	 h Up")
invalid .= neq.Call("not_a_hotkey")
invalid .= neq.Call("g Up & g Up")
invalid .= neq.Call("vk12sc123")
invalid .= neq.Call("vk123456")
invalid .= neq.Call("a up & b up")
MsgBox % invalid

Clipboard := valid "`n" invalid
ExitApp

Esc::ExitApp

isLikeHotkey(str) {
	static VALID_HOTKEY := "(\b(LButton|RButton|MButton|XButton1|XButton2|WheelDown|WheelUp|WheelLeft|WheelRight|CapsLock|Space|Tab|Enter|Return|Escape|Esc|Backspace|BS|ScrollLock|Delete|Del|Insert|Ins|Home|End|PgUp|PgDn|Up|Down|Left|Right|Numpad0|NumpadIns|Numpad1|NumpadEnd|Numpad2|NumpadDown|Numpad3|NumpadPgDn|Numpad4|NumpadLeft|Numpad5|NumpadClear|Numpad6|NumpadRight|Numpad7|NumpadHome|Numpad8|NumpadUp|Numpad9|NumpadPgUp|NumpadDot|NumpadDel|NumLock|NumpadDiv|NumpadMult|NumpadAdd|NumpadSub|NumpadEnter|F1|F2|F3|F4|F5|F6|F7|F8|F9|F10|F11|F12|F13|F14|F15|F16|F17|F18|F19|F20|F21|F22|F23|F24|LWin|RWin|Control|Ctrl|Alt|Shift|LControl|LCtrl|RControl|RCtrl|LShift|RShift|LAlt|RAlt|Browser_Back|Browser_Forward|Browser_Refresh|Browser_Stop|Browser_Search|Browser_Favorites|Browser_Home|Volume_Mute|Volume_Down|Volume_Up|Media_Next|Media_Prev|Media_Stop|Media_Play_Pause|Launch_Mail|Launch_Media|Launch_App1|Launch_App2|AppsKey|PrintScreen|CtrlBreak|Pause|Break|Help|Sleep|vk[a-fA-F0-9]{2}|sc[a-fA-F0-9]{3})\b|`;|([^\s](?!\w)))"
		 , OPTIONAL_PASSTHROUGH := "(~?)"
		 , WHITESPACE := "(\s*)"
		 , AMPERSAND := "(&)"
		 , OPTIONAL_UP := "(\s*Up)?"
		 , BOL := "^"
		 , SINGLE_HOTKEY_MODIFIERS := "([~*$+^!#<>]*)"
		 , AMPERSAND_HOTKEY := BOL WHITESPACE SINGLE_HOTKEY_MODIFIERS AMPERSAND WHITESPACE OPTIONAL_UP
		 , SINGLE_HOTKEY := BOL WHITESPACE SINGLE_HOTKEY_MODIFIERS VALID_HOTKEY WHITESPACE OPTIONAL_UP
		 , MULTI_HOTKEY := BOL WHITESPACE OPTIONAL_PASSTHROUGH VALID_HOTKEY WHITESPACE AMPERSAND WHITESPACE OPTIONAL_PASSTHROUGH VALID_HOTKEY WHITESPACE OPTIONAL_UP

	if (str = "") ; bail early
		return false

	StrReplace(strCopy := str, "&",, numAmpersands) ; count how many '&' in hotkey

	; handle case, where ampersand is a single hotkey '&::',
	; and not a joining char or left/right part of a multihotkey
	if (numAmpersands = 1 && RegExMatch(str, AMPERSAND_HOTKEY))
		numAmpersands := 0 ; this forces the ampersand hotkey to be evaluated as a single hotkey

	if numAmpersands in 0 ; single hotkey check
		return !!RegExMatch(str, SINGLE_HOTKEY)

	else if numAmpersands in 1,2,3 ; multihotkey check
		return !!RegExMatch(str, MULTI_HOTKEY)

	else ; invalid hotkey, too many & & & & ...
		return false

	throw new Exception(Format("Error: {}(""{}"") wasnt caught by any of the checks", A_ThisFunc, str))
}

assert(condition, fn, arg) {
	static MSG := "<{}>: {}(""{}"") == '{}'`n"
	return Format(MSG
			, fn.Call(arg) == condition ? "PASS" : "FAIL"
			, fn.Name
			, arg
			, condition ? "true" : "false")
}
as for the code, i copied all keys from the keylist in the docs, made the regex and tinkered with it until the tests stopped failing
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: does string look like a hotkey

23 Oct 2018, 14:25

- Thanks for the info and update.
- Btw StrReplace and RegExReplace can both be used to count strings.
- I checked through my hotkeys in my main scripts and only found a few false positives, and no false negatives.

Code: Select all

:*?://::{#} ;idle (python) - send text - autocorrect comment character
		. "`r`n" ":c*:" vTitle "::"
		. "`r`n" ":*:" vLower "::"
			. "`r`n" ":c*:" vTitle "-::"
			. "`r`n" ":*:" vLower "-::"
- I also noticed that ;q is reported as OK by the function. However if a line starts with ; this will be reported by my converter script as a comment anyhow.

- Btw I might include isLikeHotkey(), or a version of it, in the next update of 'AHK v1 to AHK v2 converter', if that's OK. Is that OK? Thanks.
AHK v1 to AHK v2 converter - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 37&t=36754
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: does string look like a hotkey

23 Oct 2018, 18:26

nice find, the issue was i didnt account for ` needing to be escaped and disappearing, which messed up the regex, allowing it to match ;::. fixed:

Code: Select all

#NoEnv
#SingleInstance Force
SetBatchLines -1

fn := Func("isLikeHotkey")
eq := Func("assert").Bind(true, fn)
neq := Func("assert").Bind(false, fn)

valid := "Testing valid hotkeys`n`n"
valid .= eq.Call("Up Up")
valid .= eq.Call("Up")
valid .= eq.Call("``") ; hotkey is meant to be single `::
valid .= eq.Call("`; Up")
valid .= eq.Call(":")
valid .= eq.Call("  	 &   & & ")
valid .= eq.Call("  	 &   & k")
valid .= eq.Call("h  	  & 		 j Up")
valid .= eq.Call("~*>$>>+!^#<g Up")
valid .= eq.Call("vk24 & vk23")
valid .= eq.Call("sc123")
valid .= eq.Call("z")
valid .= eq.Call("LCtrl & &")
valid .= eq.Call("& & &")
valid .= eq.Call("LCtrl & LCtrl")
valid .= eq.Call("Ctrl & Ctrl")
valid .= eq.Call("a & a")
valid .= eq.Call("a & b")
valid .= eq.Call("a & b up")
valid .= eq.Call("a & &")
valid .= eq.Call("& & a")
valid .= eq.Call("&")
MsgBox % valid

invalid := "Testing invalid hotkeys`n`n"
invalid .= neq.Call("") ; blank hotkey, ie ::
invalid .= neq.Call("    	*  	g &   	 h Up")
invalid .= neq.Call("not_a_hotkey")
invalid .= neq.Call("g Up & g Up")
invalid .= neq.Call("vk12sc123")
invalid .= neq.Call("vk123456")
invalid .= neq.Call("a up & b up")
invalid .= neq.Call(";")
invalid .= neq.Call(";asdq")
invalid .= neq.Call(";q")

MsgBox % invalid

Clipboard := valid "`n" invalid
ExitApp

Esc::ExitApp

isLikeHotkey(str) {
	static VALID_HOTKEY := "(\b(LButton|RButton|MButton|XButton1|XButton2|WheelDown|WheelUp|WheelLeft|WheelRight|CapsLock|Space|Tab|Enter|Return|Escape|Esc|Backspace|BS|ScrollLock|Delete|Del|Insert|Ins|Home|End|PgUp|PgDn|Up|Down|Left|Right|Numpad0|NumpadIns|Numpad1|NumpadEnd|Numpad2|NumpadDown|Numpad3|NumpadPgDn|Numpad4|NumpadLeft|Numpad5|NumpadClear|Numpad6|NumpadRight|Numpad7|NumpadHome|Numpad8|NumpadUp|Numpad9|NumpadPgUp|NumpadDot|NumpadDel|NumLock|NumpadDiv|NumpadMult|NumpadAdd|NumpadSub|NumpadEnter|F1|F2|F3|F4|F5|F6|F7|F8|F9|F10|F11|F12|F13|F14|F15|F16|F17|F18|F19|F20|F21|F22|F23|F24|LWin|RWin|Control|Ctrl|Alt|Shift|LControl|LCtrl|RControl|RCtrl|LShift|RShift|LAlt|RAlt|Browser_Back|Browser_Forward|Browser_Refresh|Browser_Stop|Browser_Search|Browser_Favorites|Browser_Home|Volume_Mute|Volume_Down|Volume_Up|Media_Next|Media_Prev|Media_Stop|Media_Play_Pause|Launch_Mail|Launch_Media|Launch_App1|Launch_App2|AppsKey|PrintScreen|CtrlBreak|Pause|Break|Help|Sleep|vk[a-fA-F0-9]{2}|sc[a-fA-F0-9]{3})\b|```;|([^\s;](?!\w)))"
		 , OPTIONAL_PASSTHROUGH := "(~?)"
		 , WHITESPACE := "(\s*)"
		 , AMPERSAND := "(&)"
		 , OPTIONAL_UP := "(\s*Up)?"
		 , BOL := "^"
		 , SINGLE_HOTKEY_MODIFIERS := "([~*$+^!#<>]*)"
		 , AMPERSAND_HOTKEY := BOL WHITESPACE SINGLE_HOTKEY_MODIFIERS AMPERSAND WHITESPACE OPTIONAL_UP
		 , SINGLE_HOTKEY := BOL WHITESPACE SINGLE_HOTKEY_MODIFIERS VALID_HOTKEY WHITESPACE OPTIONAL_UP
		 , MULTI_HOTKEY := BOL WHITESPACE OPTIONAL_PASSTHROUGH VALID_HOTKEY WHITESPACE AMPERSAND WHITESPACE OPTIONAL_PASSTHROUGH VALID_HOTKEY WHITESPACE OPTIONAL_UP

	if (str = "") ; bail early
		return false

	StrReplace(strCopy := str, "&",, numAmpersands) ; count how many '&' in hotkey

	; handle case, where ampersand is a single hotkey '&::',
	; and not a joining char or left/right part of a multihotkey
	if (numAmpersands = 1 && RegExMatch(str, AMPERSAND_HOTKEY))
		numAmpersands := 0 ; this forces the ampersand hotkey to be evaluated as a single hotkey

	if numAmpersands in 0 ; single hotkey check
		return !!RegExMatch(str, SINGLE_HOTKEY)

	else if numAmpersands in 1,2,3 ; multihotkey check
		return !!RegExMatch(str, MULTI_HOTKEY)

	else ; invalid hotkey, too many & & & & ...
		return false

	throw new Exception(Format("Error: {}(""{}"") wasnt caught by any of the checks", A_ThisFunc, str))
}

assert(condition, fn, arg) {
	static MSG := "<{}>: {}(""{}"") == '{}'`n"
	return Format(MSG
			, fn.Call(arg) == condition ? "PASS" : "FAIL"
			, fn.Name
			, arg
			, condition ? "true" : "false")
}
idk what to make of this hotstring(?) snippet uve posted. i didnt make the function match hotstrings if thats what ure asking
lastly, yes, u can do with the code as u please
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: does string look like a hotkey

23 Oct 2018, 18:29

- Thanks. The 5 examples above were not hotkeys, but were identified as hotkeys. (I need the function to identify hotkeys only and not hotstrings.)
- So, for these 5 lines, the text before the :: was incorrectly identified as a hotkey.

Code: Select all

:*?://::{#} ;idle (python) - send text - autocorrect comment character
		. "`r`n" ":c*:" vTitle "::"
		. "`r`n" ":*:" vLower "::"
			. "`r`n" ":c*:" vTitle "-::"
			. "`r`n" ":*:" vLower "-::"

;in each case above, the text before the :: is being interpreted as a hotkey:
:*?://
		. "`r`n" ":c*:" vTitle "
		. "`r`n" ":*:" vLower "
			. "`r`n" ":c*:" vTitle "-
			. "`r`n" ":*:" vLower "-
- In my conversion script, anything after 'hotkey::' should be converted. If I miss out some conversions, that's fine. If I incorrectly convert something, that could be a problem. However, one safety check, I could just say: if the text contains 2 colons, it's not a hotkey. That would work for the examples above.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: morkovka18, Shifted_Right and 173 guests