Usage:
#!d debugger settings dialog. Generates or reads from .ini file.
F12 Breaks if there is no splashtext-break; useful if no Line# Return is specified.
RCtrl continue from break; hold down for continuous advance.
Note1: At break-points, hotkeys and timers will still run.
Note2: The presence of timers other than the debug timer will prevent straightforward, line-by-line monitoring of non-timer code.
settings (in settings dialog):
use debug = allows toggling of debug mode.
debug line-by-line = if not checked, the debugger will intercept and display commands which have an execution time of at least .01 sec.
skip 400+ lines-passed alert = is displayed when each of ~400 code lines in LinesHistory execute in less than .01 sec, meaning the debugger will miss monitoring some lines.
sleep to maintain active windows (millisec) = allows adjustment in combination with A_WinDelay, for active windows seen by target code.
Line# Return of target code = used to alert on thread completed (optional).
which Lines to check = if this is used, turn off line-by-line for faster debugging. Specified lines should be Sleep 10 or higher, used as break-points.
---
To debug, place the following 2 lines at the top of the target code to be debugged, after the subroutine label:
Code:
if debug_On
GoSub Debug_Init
If you have an OnClipboardChange routine, place the following immediately after the label:
Code:
if OnClipboardChangeDeactivated OR debugRun ; or just: if debugRun
RETURN
Along with the Break/Exit-debug options in the debugger, you may also Reload from the File menu of the Ahk main window.
Here is the debugger code (an example for debugging is given at the end):
Code:
SetTitleMatchMode, 2
GoSub Load_DebuggerSettings ; place in auto-execute section
return
Load_DebuggerSettings:
if FileExist(A_ScriptDir . "\Ahk debugger settings.ini")
{
IniRead, Debug_On, %A_ScriptDir%\Ahk debugger settings.ini, preferences, Debug_On
IniRead, Debug_LineByLine, %A_ScriptDir%\Ahk debugger settings.ini, preferences, Debug_LineByLine
IniRead, Debug_skip400alert, %A_ScriptDir%\Ahk debugger settings.ini, preferences, Debug_skip400alert
IniRead, Debug_sleepToActive, %A_ScriptDir%\Ahk debugger settings.ini, preferences, Debug_sleepToActive
IniRead, Debug_line#_Return, %A_ScriptDir%\Ahk debugger settings.ini, preferences, Debug_line#_Return
IniRead, Debug_LinesToCheck, %A_ScriptDir%\Ahk debugger settings.ini, preferences, Debug_LinesToCheck
}
else
{
Debug_On = 1
Debug_LineByLine = 1
Debug_skip400alert = 0
Debug_sleepToActive = 100
Debug_line#_Return = 0
Debug_LinesToCheck = 0
FileAppend,
( LTrim
[preferences]
Debug_On = 1
Debug_LineByLine = 1
Debug_skip400alert = 0
Debug_sleepToActive = 100
Debug_line#_Return = 0
Debug_LinesToCheck = 0
), %A_ScriptDir%\Ahk debugger settings.ini
}
;msgbox,,, loaded, .5
return
#!d::
GoSub Load_DebuggerSettings
Gui 31:Destroy
Gui, 31: +AlwaysOnTop
Gui, 31: Add, Checkbox, x16 y7 h20 vDebug_On Checked%Debug_On%, use debug
Gui, 31: Add, Button, Default Hidden x153 y7, OK
Gui, 31: Add, Checkbox, x16 y29 h20 vDebug_LineByLine Checked%Debug_LineByLine%, debug line-by-line
Gui, 31: Add, Checkbox, x16 y51 h20 vDebug_skip400alert Checked%Debug_skip400alert%, skip 400+ lines-passed alert
Gui, 31: Add, Edit, x16 y76 w42 h20 vDebug_sleepToActive, %Debug_sleepToActive% ;100
Gui, 31: Add, Text, x59 y76 w164,sleep to maintain active windows seen by target code (millisec)
Gui, 31: Add, Edit, x16 y108 w42 h20 vDebug_line#_Return, %Debug_line#_Return% ;0
Gui, 31: Add, Text, x60 y108 w190, Line# Return of target code (optional).`nused to alert on thread completed
Gui, 31: Add, Edit, x16 y140 w210 h20 vDebug_LinesToCheck, %Debug_LinesToCheck% ;0
Gui, 31: Add, Text, x16 y162 w210, which Lines to check (0 or blank = all)`nuse commas for 2+
, spaces optional.`nSet specified Lines as Sleep 10 or greater.
Gui, 31: Show, h210 w250, Debug settings
return
31ButtonOK:
31GuiClose:
31GuiEscape:
Gui, 31:Submit
Gui, 31:Destroy
IniWrite, %Debug_On%, %A_ScriptDir%\Ahk debugger settings.ini, preferences, Debug_On
IniWrite, %Debug_LineByLine%, %A_ScriptDir%\Ahk debugger settings.ini, preferences, Debug_LineByLine
IniWrite, %Debug_skip400alert%, %A_ScriptDir%\Ahk debugger settings.ini, preferences, Debug_skip400alert
IniWrite, %Debug_sleepToActive%, %A_ScriptDir%\Ahk debugger settings.ini, preferences, Debug_sleepToActive
IniWrite, %Debug_line#_Return%, %A_ScriptDir%\Ahk debugger settings.ini, preferences, Debug_line#_Return
IniWrite, %Debug_LinesToCheck%, %A_ScriptDir%\Ahk debugger settings.ini, preferences, Debug_LinesToCheck
return
Debug_Init:
WinShow, - AutoHotkey v ahk_class AutoHotkey ; in case DetectHiddenWindows is off. exclude tooltips
WinRestore, - AutoHotkey v ahk_class AutoHotkey ; prepare for WinMove
WinMove, - AutoHotkey v ahk_class AutoHotkey,, 0, 590, 1024, 150 ; set window to debug-style shape
WinActivate, - AutoHotkey v ahk_class AutoHotkey
Thread, Interrupt, 0 ; always allow debug timer
if Debug_LineByLine
SetBatchLines 1 ; allows line-by-line debug
SetTimer Debug, 1
sleep 300 ; SetBatchLines=1 requires sufficient sleep to properly take effect
return
Debug:
Debug_line#_Return2 := A_LineNumber + 2
Debug()
return
; adding/deleting lines in this code block requires adjusting Debug_line#_Close
Debug()
{
Debug_line#_Close := A_LineNumber + 110 ; Line of closing brace *******
global Debug_line#_Return ; return of target code being debugged
global Debug_line#_Return2 ; return of debug timer routine
global Debug_LinesToCheck
global debugRun ; for OnClipboardChange handling
global Debug_On, Debug_skip400alert, Debug_sleepToActive ; settings
static Debug_line#_Return3
debugRun ++
sleep Debug_sleepToActive ; wait for window activated by target code
winA := WinActive("A") ; store ID of active window
clipSaved_debug := clipboard
clipboard =
ListLines
WinActivate, - AutoHotkey v ahk_class AutoHotkey ; exclude Ahk tooltips
ControlSend,, +^{end}^c, - AutoHotkey v ahk_class AutoHotkey ; call main window
codeToDebug := clipboard
ControlSend,, ^{end}{up 11}+{end}^c, - AutoHotkey v ahk_class AutoHotkey ; up # to last line before debug call
Loop
{
code_Line := clipboard
code_Line := StringL( code_Line, InStr(code_Line, ":")-1 )
if code_Line is number
BREAK
else
ControlSend,, {up}{home}+{end}^c, - AutoHotkey v ahk_class AutoHotkey
}
clipboard := clipSaved_debug
if (code_Line = Debug_line#_Return2) ; dont display Return of debug timer
{
Loop 400
a=1
WinActivate, ahk_id %winA%
WinWaitActive, ahk_id %winA% ; restore active window seen by target code
Debug_line#_Return3 := A_LineNumber + 2
if ! GetKeyState("F12", "p")
RETURN
}
Loop 4
SendMessage, 0x115, 0, 0, Edit1, - AutoHotkey v ahk_class AutoHotkey
If !Debug_skip400alert & (debugRun >1)
&& !InStr(codeToDebug, "`n" . Debug_line#_Close . ": }")
&& !InStr(codeToDebug, "`n" . Debug_line#_Return3 . ": ")
{
msgbox, 4353, debug,
( LTrim
More than 400 lines were executed in 0.00 sec.
Some lines of code being debugged have scrolled past in the Line history buffer.`n
1) Verify that Debug_line#_Close matches the line # of the closing } in Debug()`n
2) Use Thread, Interrupt, 0 before SetTimer, Debug, 1`n
3) A sleep of 10 or greater may be needed logically prior to Line %code_Line%`n
[This alert can be deactivated in the debug settings dialog.]`n
Continue debug?
)
IfMsgBox, Cancel
{
debugRun =
SetTimer Debug, off
WinMove, - AutoHotkey v ahk_class AutoHotkey,, 153, 153, 768, 558
Loop 400 ; for code tracing readability
a=1
RETURN
}
}
If (debugRun > 1) AND (InStr(codeToDebug, "`n" . Debug_line#_Return . ": " ) )
{
SplashText("*thread completed - press Space to exit", 0, 350)
Keywait Space, D
Keywait Space
debugRun =
SetTimer Debug, off
SplashTextOff
WinMove, - AutoHotkey v ahk_class AutoHotkey,, 153, 153, 768, 558
WinMinimize - AutoHotkey v ahk_class AutoHotkey
Loop 400
a=1
RETURN
}
/* ; display lines in preferred editor
ControlSend,, ^g, <editor_caption>
sleep 50 ; use higher Priority to prevent interruption
ControlSend,, %code_Line%{Enter}, <editor_caption>
*/
if ( ! Debug_LinesToCheck ) OR In(code_Line, Debug_LinesToCheck) OR GetKeyState("F12", "p")
{
SplashText("variables current to Line " . code_Line
. " press RCtrl to continue. RCtrl.RShift to exit debug", 0, 500)
WinSet, Enable,, exit debug
Keywait RCtrl, D
sleep 150
if GetKeyState("RShift", "p") AND GetKeyState("RCtrl", "p")
{
; needed to exit. using before Keywait above disallows other threads
Thread, Priority, 999
debugRun =
SetTimer, Debug, off
WinRestore, - AutoHotkey v ahk_class AutoHotkey
WinMove, - AutoHotkey v ahk_class AutoHotkey,, 153, 153, 768, 558
WinMinimize - AutoHotkey v ahk_class AutoHotkey
}
SplashTextOff
}
Loop 400 ; for code tracing readability & to prevent false thread-complete alert
a=1
WinActivate, ahk_id %winA%
WinWaitActive, ahk_id %winA%,, 1.2 ; restore active window seen by target code
} ; this line number must match Debug_line#_Close *******
StringL(in, charsToKeep, charsToTrim="")
{
StringLeft, out, in, %charsToKeep%
if charsToTrim
StringTrimLeft, out, out, %charsToTrim%
return out
}
In( a, b ) ; If a in b list
{
Loop, Parse, b, `,, %A_Space%%A_Tab%
if a = %A_LoopField%
Return 1
}
SplashText(title="", timeout="", width="", height="", text="") ; default timeout 700
{
SplashTextOn, %Width%, %Height%, %Title%, %Text%
if timeout =
sleep 700
else if timeout = 0 ; leave on until replaced or turned off later
RETURN
else
sleep %timeout%
SplashTextOff
return
}
!-:: ; example code for debug
if debug_On
GoSub Debug_Init
; the lines above prepare the debug
; the lines below are the code to debug (example)
Loop 3
{
sleep 10
a=1
b=2
c=3
d=4
}
Return
i will probably improve the code to optionally show last 10 lines executed, or last N lines specified by the user. Also, it would be good to have a display of target variables, selected by the user beforehand. Currently, ^v to display all the script's variables, will have to suffice. I will also look into removing the timer-limitation in line-by-line mode.