I’ve written script that contains numerous hotkeys (general structure is as below). I would like to create another one that when pressed displays a list of all of the hotkeys and their corresponding descriptions that the script contains in a nice, formatted table.
The formatting and display are tenuous since AutoHotkey’s output is limited to message-boxes, but possible. More problematic is getting the hotkeys and corresponding descriptions.
The hotkeys all call the same function with different arguments. I considered adding a variable to the function so that depending on the value, the function either performs the normal function when triggered by the normal hotkeys, or builds a string or something when triggered from the special display hotkey.
I cannot figure out a way to programmatically access the script’s hotkeys at all. I checked the docs and there don’t seem to be any A_ variables that can be used for this purpose, nor does the Hotkey command lend itself well (it can be used to test if a hotkey exists, but looping through the innumerable combinations is, at best, tedious).
Failed attempts:
I tried using Elliot’s suggestion of parsing the script itself (replacing the path with %A_ScriptFullPath%, and while it does work for a raw script, it does not when the script is compiled
I tried assigning the entire hotkey section of the script to a variable as a continuation section and then parsing the variable and creating hotkeys using the Hotkey command. This worked well right up until the last part because the Hotkey command cannot take arbitrary commands as the destination and requires existing labels.
The ListHotkeys command is not applicable because it only displays the hotkeys as plain text in the control window.
Does anyone know how I can display a list of the hotkeys and either their corresponding arguments or comments?
Example script:
SomeFunc(foobar)
{
MsgBox %foobar%
}
!^#A::SomeFunc("a") ; blah
^+NumpadMult::SomeFunc("c") ; blivet
^+!#`::SomeFunc("b") ; baz
^#Space::SomeFunc("d") ; ermahgerd
…
Example desired “outputs”:
C+A+ W+ A a | C+ S+ NumpadMult b
------------------+----------------------
C+A+S+W+ ` c | C+ W+ Space d
or
Ctrl Alt Shift Win Key Action
-----------------------------------------
× × × A blah
× × NumpadMult baz
× × × × ` blivet
× × Space ermahgerd
etc.
The only thing I can think of is to read each line of your script individually and parse it. This code reads your script (script.ahk) one line at a time and parses it. This should get you started. Additionally, you could parse the line to check for the modifiers as well.
Loop
{
FileReadLine, line, C:\script.ahk, %A_Index%
if ErrorLevel
break
If Instr(line, "::")
{
StringSplit, linearray, line, ::,
key := linearray1
StringSplit, commandarray, linearray3, `;
action := commandarray2
hotkeyline := "key: " . key . "`tAction: " . action
final .= hotkeyline . "`r"
}
}
msgbox % final
return
I found a solution. It is not perfect (or ideal), and hopefully a proper, built-in method will become available in the future, but it works well (enough) and for raw and compiled scripts.
What I did was to use the FileInstall command which tells the compiler to add a file to the executable (and extract it when run).
Sadly, the FileInstall command will not allow the use of variables for the source file, so I cannot simply include the script itself (FileInstall, %A_ScriptFullPath%, %A_Temp%\%A_ScriptName%, 1).
As a work-around, I ended up extracting all of the desired hotkeys to a second file which I then parse as Elliot suggested, then delete, and #Include at the end of my script (it must be at the end since hotkeys will terminate the autoexecute section).
;;;;; Test.ahk ;;;;;
; Add hotkey file to executable and extract to Temp directory at runtime
FileInstall, Hotkeys.ahk, %A_Temp%\Hotkeys.ahk, 1
Loop
{
;Read a line from the extracted hotkey script and quit if error
FileReadLine, line, %A_Temp%\Hotkeys.ahk, %A_Index%
if ErrorLevel
break
;Trim whitespace
line=%line%
; Parse the line as in Elliot’s answer, but with tweaks as necessary
ParseHotkey(line)
…
}
FileDelete, %A_Temp%\Hotkeys.ahk ; Delete the extracted script
DisplayHotkeys() ; I ended up bulding and using a GUI instead
#Include, Hotkeys.ahk ; It is included at compile-time, so no A_Temp
;;;;; Hotkeys.ahk ;;;;;
z::MsgBox foo
y::MsgBox bar
Related
I'm fairly new to autohotkey and ran into a problem recently when trying to pass variables as parameters for the "run" command in autohotkey. Can anyone show me what I'm missing or is this a bug?
Gui, Setup:Add, Edit, w100 vProgram
Gui, Setup:Add, Button, Default w100 gSubmit, OK
Gui, Setup:Show,, Setup
WinWaitClose, Setup
ExitApp
Submit:
Gui, Setup:Submit
RunStuff()
RunStuff() {
Run, %Program%
}
I've even tried changing the line to this:
RunStuff() {
run, Program
}
I would really appreciate the help, thanks in advance.
First things first, while
WinWaitClose, Setup
ExitApp
Is maybe smart thinking, you're supposed to do this with the GuiClose event by just defining a function (or a label) with the name GuiClose.
GuiClose()
{
ExitApp
}
Also, giving the gui a name is redundant, although if you're planning on adding more guis, fair enough.
And to run cmd commands, you'd start off cmd with the /c(docs) switch.
Your example netsh wlan connect ssid=%networkname% name=%networkname% would be done like this:
networkname := "name"
Run, %A_ComSpec% /c netsh wlan connect ssid=%networkname% name=%networkname%
Run, % A_ComSpec " /c netsh wlan connect ssid=" networkname " name=" networkname
The first line is in legacy syntax, I wouldn't really recommend it.
The second line is in expression syntax.
The built in variable A_ComSpec(docs) contains the path to cmd.exe.
Example program based on your code to open a file in notepad:
Gui, Setup:Add, Edit, w300 vFilePath, % "C:\Users\User\Desktop\this is a text file.txt"
Gui, Setup:Add, Button, Default w100 gSubmit, OK
Gui, Setup:Show, , Setup
return
Submit:
Gui, Setup:Submit
Run, % "notepad.exe """ FilePath """"
return
GuiClose()
{
ExitApp
}
Note how the file path includes spaces, so the argument needs to wrapped in quotes.
"""" may seem weird, but a quote is escaped with another quote in AHK. So outer quotes specify that you're writing the string, and inner "" is just one quote escaped. So this produces ".
Legacy syntax vs modern expression syntax can be a bit confusing when you're learning AHK. You'll see a lot of legacy syntax when you look up stuff. This is mainly because AHK was much more popular years and years ago (when legacy syntax was the thing to use).
To get started off on the legacy vs modern expression differences, here's a pretty good documentation page:
https://www.autohotkey.com/docs/Language.htm
EDIT:
Answer to the new problem that as added in via an edit to the OP.
RunStuff() {
Run, %Program%
}
The variable Program is not defined in the function's scope.
Lets consider this example code
var1 := 1
var2 := 2
global var3 := 3
function()
return
function()
{
global var2
MsgBox, % "var1: " var1 "`nvar2: " var2 "`nvar3: " var3 "`n"
}
var1 is not defined in the function's scope, so nothing will be printed in the message box.
var2 will be used from outside the function's scope because of the line global var2.
var3 would also be used from outside of the function scope because var3 was defined as super global. Super global isn't really recommended, because anything anywhere will use that variable then. Quite easy to run into problems on larger scripts, but it's convenient for smaller scripts where you know what you'll be doing and don't have any external libraries for example.
You can read the related documentation from here:
https://www.autohotkey.com/docs/Functions.htm#Locals
It seems that you think your problem is with the gui output.
I think you might want to check things in a different way:
You could create a script to analyze the your run inputs.
For example:
Script #1, let's call it "1.ahk"
Run, 2.ahk param1 param 2 "param 3"
Script #2, let's call it "2.ahk"
txt := "Params as seen by ahk:`n"
for i, param in A_Args
txt .= i " = " param "`n"
MsgBox % txt
By running script "1.ahk" you can see the message that "2.ahk" creates:
Params as seen by ahk:
1 = param1
2 = param
3 = 2
4 = param 3
Which I think it's quite telling about how it understands spaces, and how quotes prevent splitting arguments.
Now if you run your gui outputs against "2.ahk" you might see something different to what you saw before.
RunStuff() {
Run % start "" "C:\blabla\prog.exe" -windowstyle minimized
}
I normally use the United States-International keyboard layout. This layout has several keys set as "dead" keys for diacritic marks - for example, pressing ^ is a dead key; it appears to do nothing until the next key is hit; if that key is one that the circumflex is an allowable diacritic, it replaces it with the marked key - that is, if I press ^ then a, I will get â - but if I press a key that it's not an allowed diacritic for, I will get the circumflex followed by the letter, e.g., ^ follows by h gives me ^h.
I wrote a AHK script that adds the diacriticalized characters for Esperanto (see below). It used to work "transparently" and matched the behavior described above. However, recently, the behavior seems to have changed: it no longer "swallows" the diacritic, and inserts a backspace before inserting the character desired.
In other words, if I type "The Esperanto character that sounds like English 'ch' is " and then type ^ then c, it replaces the space following "is" with the ĉ, and on the next keystroke, whatever it is, acts like I had hit ^ then that key.
Why? and How do I fix this?
#Hotstring ? C *
; Esperanto diacriticalized characters
::^c::ĉ
::^C::Ĉ
::^g::ĝ
::^G::Ĝ
::^h::ĥ
::^H::Ĥ
::^j::ĵ
::^J::Ĵ
::^s::ŝ
::^S::Ŝ
::~u::ŭ
::~U::Ŭ
Don't know if I maybe missed something simple with hotstrings, but I couldn't really make it work without trying to do some even further trickery.
I figured an InputHook(docs) implementation could work pretty well.
It might be overkill/stupid though, since it basically just creates a custom implementation for a hotstring. But well, at least it works.
key_map := { "c": "ĉ"
, "g": "ĝ"
, "h": "ĥ"
, "j": "ĵ"
, "s": "ŝ"
, "u": "ŭ" }
ih := InputHook("V")
ih.OnChar := Func("OnChar")
ih.Start()
OnChar(_, char)
{
if (StrLen(char) != 2 || SubStr(char, 1, 1) != "^" || !(key := diacriticalize(SubStr(char, 2))))
return
fObj := Func("SendReplacement").Bind(key)
SetTimer, % fObj, -0
}
diacriticalize(key)
{
global key_map
if key is upper
return Format("{:U}", key_map[key])
else
return key_map[key]
}
SendReplacement(key)
{
SendInput, % "{BS 2}" key
}
So what's happening?
First a map for the key replacements is defined.
Adding any extra dead key combinations for ^ will work just fine.
The input hook is created with only the V(docs) option.
This makes it so that it doesn't consume input while processing it.
Then, with .OnChar(docs) we define a function that runs every time the input receives a new character.
The functions always receives just one character, except when a dead key is used it'll receive e.g ^c.
This is why we check if the input length is two and why we use SubStr()(docs) to transform ^c to just c.
SubStr(char, 1, 1) != "^" also ensures that the pressed deadkey was ^, and not e.g ¨. Otherwise ¨c would produce ĉ.
Then in the user defined function diacriticalize() we return the corresponding diacriticalized key from the key_map (if possible). If the input key was in uppercase, return the diacriticalized key in uppercase as well.
If there is no matching key in the key_map, nothing is returned. Which makes the || !(key := ...) part do its trick to also return if the input key wasn't valid.
Then the timer(docs) trickery is done just to execute the replacement outside of the OnChar() function in another thread to avoid problems with send command running too early.
Basically the period -0 just means to run once immediately.
The function which the timer will is defined as a function object that has a parameter (the key) bound to it with .Bind()(docs).
OK, I’m not sure why it works this way, but I was able to get it working by turning off the automatic backspacing and manually adding my own. The revised AHK script is as follows:
#Hotstring ? C * B0
; Acts only as a supplement to a keyboard that (a) does not
; have these characters defined _and_ uses ^ and ~ as "dead"
; keys to apply accents.
::^c::{bs 2}ĉ
::^C::{bs 2}Ĉ
::^g::{bs 2}ĝ
::^G::{bs 2}Ĝ
::^h::{bs 2}ĥ
::^H::{bs 2}Ĥ
::^j::{bs 2}ĵ
::^J::{bs 2}Ĵ
::^s::{bs 2}ŝ
::^S::{bs 2}Ŝ
::~u::{bs 2}ŭ
::~U::{bs 2}Ŭ
::^::^
::~::~
The B0 in the #Hotstring directive turns off the automatic backspacing. With that option in effect in the original script, typing ^c would result in ^cĉ, so by inserting two backspaces before it ({bs 2}), I get rid of the extraneous ^c before inserting the ĉ.
The last two lines, replacing the caret and tilde with themselves, don’t have an obvious explanation for the reason that they're needed, but they ensure that the behavior is consistent with the standard deadkey usage, so that if I type ^spacec I get the expected ^c instead of an unexpected ĉ.
I have a txt file named subroutines.ahk in it is the below hotstring.
::btw::Thank you for the help
I know if I want to run my hotstring from another script all I have to do is include it in that script using:
#include subroutines.ahk
But I dont want to do that Instead from another script I want to Loop read the contents of subroutines.ahk and load the hotstring variables that way .
Loop, read,subroutines.ahk
{
Loop, parse, A_LoopReadLine, %A_Tab%
{
MsgBox, Field number %A_Index% is %A_LoopField%.
}
}
I would really appreciate your help on how I can load my hotstring variables into another script that way.
I plan to encode the hotsrings in subroutines.ahk and than decode the contents of the subroutines.ahk in the second program and load it into memory.
In the above example though i am first trying to figure out how I can loop read the txt file and run the hot strings from there.
Demonstrates the use of hotkeys that map to dynamic values
; Replace hash values with whatever method you use to decrypt your hotstrings
decrypted := {abc: "alpha", def: "beta"}
::abc::
Send % decrypted["abc"]
return
::def::
Send % decrypted["def"]
return
When you type "abc", "alpha" appears instead
# directives are processed only once when the script is launched. To work around this limitation, you can use reload to execute dynamically generated script that includes # directives.
Code sample:
When F1 is pressed, use the contents of "subroutines.ahk" to generate and run code that utilizes # directives:
F1:: reload_hotstrings()
#Include temp.ahk
reload_hotstrings()
{
FileDelete temp.ahk
loop read, subroutines.ahk
{
MsgBox Hotstring number %A_Index% is %A_LoopReadLine%.
FileAppend %A_LoopReadLine%`n, temp.ahk
}
reload
}
Alternatively, if you want to automatically generate hotkeys code whenever autohotkey starts:
I'm using source.dat for your subroutines.ahk file
Code sample:
FileGetTime source_time, source.dat
FileGetTime compiled_time, compiled.ahk
; calculate time difference between source and compiled files
EnvSub source_time, %compiled_time%, Seconds
; compile and run if source newer
if source_time > 0
{
FileDelete compiled.ahk
loop read, source.dat
{
FileAppend %A_LoopReadLine%`n, compiled.ahk
}
reload
}
#Include compiled.ahk
I have the below simple code, which sends keystrokes for text in the clipboard with a 15ms delay in between characters (I use this to traverse huge lists of treeview elements).
Issue: If I have copied 'text1' to clipboard, followed by 'text2', this script outputs 'text1text2' instead of 'text2' alone.
If I reload the script, then it prints 'text2'.
Is there a mistake in the below code, or is it a bug in implementing %clipboard% in Autohotkey 1.1.14.03 ?
#v::
textToType=" "
textToType=%clipboard%
LoopCount:=StrLen(textToType)
;StringLen, LoopCount, textToType
Array%LoopCount%:=textToType
loop %LoopCount%
{
theChar:=Array%A_Index%
Send %theChar%
sleep 15
}
return
Update: Thanks for pointing out smarter ways of doing this, but I would still like to figure out what is wrong in the above piece of code.
Update 2:
The mistake was in my understanding of the AHK syntax. Array%LoopCount%:=textToType assigns the whole string value in textToType to the (LoopCount)th STRING element of the STRING array named 'Array'.
Update 3:
(Thanks #John Y for clarifying)
Actually, there's no "declared" array at all, in a traditional sense. You just have a bunch of individual variables, dynamically created as needed, that happen to have names with numbers at the end. Array1 and Array2 are not elements in some Array object. They are just two completely independent variables. AutoHotkey provides a way to glue numbers onto the ends of names, so you can use them like an array.
The reason your script doesn't work properly is because you're using a pseudo-array to store different words from your clipboard.
I've commented up your code to explain what it does:
#v::
textToType := "" ; Empty variable
textToType := Clipboard ; Move clipboard into variable
; Get lenght of the word
; Used as array index / loop count
LoopCount := StrLen(textToType)
; Put the clipboard in an array at index 'LoopCount'
Array%LoopCount% := textToType
; Loop through the array as many times
; as the string is long
Loop % LoopCount
{
; Retrieve the word at this index in the array
theChar := Array%A_Index%
; Send the whole word
Send, % theChar
sleep 15
}
return
Instead of sending each character at a time, you're sending whole words from specific indexes in your Array array.
Say you copy the word Dragon, that word is 6 letters long. So you'd put that in Array6, then you'd loop through your array 6 times using the same variable. At which point the loop would take each index at a time and move it into theChar. On your 6th lap in the loop you'd put Array6 into theChar and print the whole word at once.
Then you copy the word Stackoverflow. That's going to go into Array13, and we're going to loop 13 times. On the 6th lap we're going to print out Dragon which is in Array6, and then keep going until we reach 13 where we'll print Stackoverflow since that is in Array13.
So that's why your script isn't doing what you want it to. Hopefully this helps a little.
See the code sample alpha bravo posted, that's the correct way of achieving what you want to do.
keep it simple
#v::
loop Parse, Clipboard
{
Send %A_LoopField%
sleep 15
}
return
There must be a bug in implementation of clipboard assignment in AHK. With the below code, the behaviour of AHK is that everytime the value of dir is accessed, AHK fetches the latest value from clipboard, instead of fetching the value of dir at the time the script was activated.
; Remove all CR+LF's from the clipboard contents:
dir = %clipboard%
sleep 100
dir := StrReplace(dir, "`r`n")
EDIT:
To fix this, I added 1 second sleep before clipboard assignment code:
sleep 1000
; Remove all CR+LF's from the clipboard contents:
dir = %clipboard%
dir := StrReplace(dir, "`r`n")
100 millisecond sleep didn't seem to work.
Accessing value of dir now only gives value of last clipboard assignment at activation.
I am using vim to edit a shell script (did not use the right coding standard). I need to change all of my variables from camel-hum-notation startTime to caps-and-underscore-notation START_TIME.
I do not want to change the way method names are represented.
I was thinking one way to do this would be to write a function and map it to a key. The function could do something like generating this on the command line:
s/<word under cursor>/<leave cursor here to type what to replace with>
I think that this function could be applyable to other situations which would be handy. Two questions:
Question 1: How would I go about creating that function.
I have created functions in vim before the biggest thing I am clueless about is how to capture movement. Ie if you press dw in vim it will delete the rest of a word. How do you capture that?
Also can you leave an uncompleted command on the vim command line?
Question 2: Got a better solution for me? How would you approach this task?
Use a plugin
Check the COERCION section at the bottom of the page:
http://www.vim.org/scripts/script.php?script_id=1545
Get the :s command to the command line
:nnoremap \c :%s/<C-r><C-w>/
<C-r><C-w> gets the word under the cursor to command-line
Change the word under the cursor with :s
:nnoremap \c lb:s/\%#<C-r><C-w>/\=toupper(substitute(submatch(0), '\<\#!\u', '_&', 'g'))/<Cr>
lb move right, then to beginning of the word. We need to do this to get
the cursor before the word we wish to change because we want to change only
the word under the cursor and the regex is anchored to the current cursor
position. The moving around needs to be done because b at the
start of a word moves to the start of the previous word.
\%# match the current cursor position
\= When the substitute string starts with "\=" the remainder is interpreted as an expression. :h sub-replace-\=
submatch(0) Whole match for the :s command we are dealing with
\< word boundary
\#! do not match the previous atom (this is to not match at the start of a
word. Without this, FooBar would be changed to _FOO_BAR)
& in replace expressions, this means the whole match
Change the word under the cursor, all matches in the file
:nnoremap \a :%s/<C-r><C-w>/\=toupper(substitute(submatch(0), '\<\#!\u', '_&', 'g'))/g<Cr>
See 3. for explanation.
Change the word under the cursor with normal mode commands
/\u<Cr> find next uppercase character
i_ insert an underscore.
nn Search the last searched string twice (two times because after exiting insert mode, you move back one character).
. Repeat the last change, in this case inserting the underscore.
Repeat nn. until all camelcases have an underscore added before them, that is, FooBarBaz has become Foo_Bar_Baz
gUiw uppercase current inner word
http://vim.wikia.com/wiki/Converting_variables_to_camelCase
I am not sure what you understand under 'capturing movements'. That
said, for a starter, I'd use something like this for the function:
fu! ChangeWord()
let l:the_word = expand('<cword>')
" Modify according to your rules
let l:new_var_name = toupper(l:the_word)
normal b
let l:col_b = col(".")
normal e
let l:col_e = col(".")
let l:line = getline(".")
let l:line = substitute(
\ l:line,
\ '^\(' . repeat('.', l:col_b-1) . '\)' . repeat('.', l:col_e - l:col_b+1),
\ '\1' . l:new_var_name,
\ '')
call setline(".", l:line)
endfu
As to leaving an uncompleted command on the vim command line, I think you're after
:map ,x :call ChangeWord(
which then can be invoked in normal mode by pressing ,x.
Update
After thinking about it, this following function is a bit shorter:
fu! ChangeWordUnderCursor()
let l:the_word = expand('<cword>')
"" Modify according to your rules
let l:new_var_name = '!' . toupper(l:the_word) . '!'
normal b
let l:col_b = col(".")
normal e
let l:col_e = col(".")
let l:line = getline(".")
exe 's/\%' . l:col_b . 'c.*\%' . (l:col_e+1) .'c/' . l:new_var_name . '/'
endfu