; ; SciTE4AutoHotkey Script Debugger ; ;TillaGoto.iIncludeMode = 0x10111111 ;{ Auto-Execute Section #SingleInstance Ignore #NoTrayIcon SetBatchLines, -1 SetWorkingDir, %A_ScriptDir% DetectHiddenWindows, On global g_appTitle := "SciTE4AutoHotkey Debugger" ADM_SCITE := 0x1010 ATM_OFFSET := 0x1000 ATM_STARTDEBUG := ATM_OFFSET+0 ATM_STOPDEBUG := ATM_OFFSET+1 ATM_DRUNTOGGLE := ATM_OFFSET+4 SciControl_InitConstants() global A_Args := [] Loop, %0% A_Args.Push(%A_Index%) if A_IsCompiled { MsgBox, 16, %g_appTitle%, This program *must* be a uncompiled script! ExitApp } if A_Args.Length() = 0 { MsgBox, 16, %g_appTitle%, You mustn't run this script directly! ExitApp } ; Get the COM SciTE object oSciTE := GetSciTEInstance() if !oSciTE { MsgBox, 16, %g_appTitle%, SciTE must be running! ExitApp } global dbgTextFont := oSciTE.ResolveProp("default.text.font") dbgAddr := oSciTE.ResolveProp("ahk.debugger.address") dbgPort := oSciTE.ResolveProp("ahk.debugger.port")+0 dbgCaptureStreams := !!oSciTE.ResolveProp("ahk.debugger.capture.streams") global dbgMaxChildren := oSciTE.ResolveProp("ahk.debugger.max.obj.children")+0 global dbgMaxData := oSciTE.ResolveProp("ahk.debugger.max.data")+0 if A_Args[1] = "/attach" bIsAttach := true else { AhkExecutable := A_Args.RemoveAt(1) IfNotExist, %AhkExecutable% { MsgBox, 16, %g_appTitle%, The AutoHotkey executable doesn't exist! ExitApp } Loop, Files, %AhkExecutable% { AhkExecutable := A_LoopFileLongPath break } ahkType := AHKType(AhkExecutable) if ahkType = FAIL { MsgBox, 16, %g_appTitle%, Invalid AutoHotkey executable! ExitApp } if ahkType = Legacy { MsgBox, 16, %g_appTitle%, Debugging is not supported in legacy versions of AutoHotkey (prior to v1.1). ExitApp } } ; Get the HWND of SciTE and its Scintilla control scitehwnd := oSciTE.SciTEHandle ControlGet, scintillahwnd, Hwnd,, Scintilla1, ahk_id %scitehwnd% ; Get the SciTE path SciTEPath := oSciTE.SciTEDir ; Get the script to debug szFilename := !bIsAttach ? oSciTE.CurrentFile : SelectAttachScript(AttachWin, Dbg_PID) if szFilename = ExitApp ; Do not allow debugging neither the toolbar nor the debugger itself if InStr(szFilename, SciTEPath "\toolbar\") = 1 || (szFilename = A_ScriptFullPath) { MsgBox, 48, %g_appTitle%, Debuging SciTE4AutoHotkey's debugger and toolbar scripts is not supported. ExitApp } ; Check if the toolbar is running ControlGet, toolbarhwnd, Hwnd,, AutoHotkeyGUI1, ahk_id %scitehwnd% if toolbarhwnd = { MsgBox, 16, %g_appTitle%, Can't find the toolbar window! ExitApp } OnExit, GuiClose ; activate an OnExit trap Gui, Show, Hide, SciTEDebugStub ; create a dummy GUI that SciTE will speak to ; Run SciTE WinActivate, ahk_id %scitehwnd% Hotkey, ^!z, CancelSciTE ToolTip, Waiting for SciTE to connect...`nPress Ctrl-Alt-Z to cancel SciTEConnected := false OnMessage(ADM_SCITE, "SciTEMsgHandler") SciTE_Connect() Hotkey, ^!z, Off ; Run AutoHotkey and wait for it to connect ToolTip, Waiting for AutoHotkey to connect... ; Initialize variables Dbg_OnBreak := true Dbg_HasStarted := false Dbg_IsClosing := false Dbg_ExitByDisconnect := false Dbg_ExitByGuiClose := false Dbg_WaitClose := false Dbg_StackTraceWin := false Dbg_VarWin := false Dbg_StreamWin := false Dbg_BkList := [] ; Set the DBGp event handlers DBGp_OnBegin("OnDebuggerConnection") DBGp_OnBreak("OnDebuggerBreak") DBGp_OnStream("OnDebuggerStream") DBGp_OnEnd("OnDebuggerDisconnection") ; Now really run AutoHotkey and wait for it to connect Dbg_Socket := DBGp_StartListening(dbgAddr, dbgPort) ; start listening SplitPath, szFilename,, szDir allArgs := ObjJoin(A_Args,, """") if !bIsAttach Run, "%AhkExecutable%" /Debug=%dbgAddr%:%dbgPort% "%szFilename%" %allArgs%, %szDir%,, Dbg_PID ; run AutoHotkey and store its process ID else { ; Set the Last Found Window WinWait, ahk_id %AttachWin% ; Get PID of the AutoHotkey window WinGet, Dbg_PID, PID ; Tell AutoHotkey to debug PostMessage, DllCall("RegisterWindowMessage", "str", "AHK_ATTACH_DEBUGGER"), DllCall("ws2_32\inet_addr", "astr", dbgAddr), dbgPort } while (Dbg_AHKExists := Util_ProcessExist(Dbg_PID)) && Dbg_Session = "" ; wait for AutoHotkey to connect or exit Sleep, 100 ; avoid smashing the CPU DBGp_StopListening(Dbg_Socket) ; stop listening if bIsAttach { Dbg_GetStack() SciTE_UpdateCurLineOfCode() } if !Dbg_AHKExists { Dbg_ExitByDisconnect := true ; tell our message handler to just return true without attempting to exit SciTE_Disconnect() OnMessage(ADM_SCITE, "") ; disable the SciTE message handler OnExit ; disable the OnExit trap ExitApp ; exit } if Dbg_Lang != AutoHotkey { ; Oops, wrong language, we've got to exit again ToolTip MsgBox, 16, %g_appTitle%, Invalid language: %Dbg_Lang%. Dbg_ExitByDisconnect := true ; tell our message handler to just return true without attempting to exit SciTE_Disconnect() OnMessage(ADM_SCITE, "") ; disable the SciTE message handler OnExit ; disable the OnExit trap ExitApp ; exit } ; Show the splash ToolTip, Ready to debug! SetTimer, RemoveTooltip, -250 ; Reset saved breakpoints PostMessage, 0x111, 1135, 0,, ahk_id %scitehwnd% ; Main loop while !Dbg_IsClosing ; while the debugger is active { Sleep, 100 ; do I really need to repeat the smashing comment over and over? IfWinNotExist, ahk_id %scitehwnd% ; oops, the user closed the SciTE window { if !Dbg_ExitByDisconnect DBGp_CloseDebugger(true) ; force closing break } if !Util_ProcessExist(Dbg_PID) { Dbg_ExitByDisconnect := true SciTE_Disconnect() break } } if Dbg_ExitByGuiClose ; we've got to tell SciTE that we are leaving { Dbg_ExitByDisconnect := true ; tell our message handler to just return true without attempting to exit SciTE_Disconnect() } OnMessage(ADM_SCITE, "") ; disable the SciTE message handler OnExit ; disable the OnExit trap ExitApp CancelSciTE: OnExit ExitApp RemoveTooltip: ToolTip return ;} ;{ Script Attaching SelectAttachScript(ByRef outwin, ByRef outpid) { global SciTEPath oldTM := A_TitleMatchMode, oldHW := A_DetectHiddenWindows SetTitleMatchMode, 2 DetectHiddenWindows, On WinGet, w, List, - AutoHotkey ahk_class AutoHotkey,, %A_ScriptFullPath% Gui, +LabelAttGui +ToolWindow +AlwaysOnTop Gui, Add, ListView, x0 y0 w640 h240 +NoSortHdr -LV0x10 gAttGuiSelect, HWND|Name i := 0 Loop, % w { hwnd := w%A_Index% WinGetTitle, ov, ahk_id %hwnd% if InStr(ov, SciTEPath) ; Do not allow debugging SciTE4AutoHotkey itself continue if !RegExMatch(ov, "v([0-9.]+)(-\S+)?$", q) ; Make sure it has a correctly-formed version number continue if q1 < 1.1.00.00 ; Make sure it is NOT a legacy AutoHotkey version continue LV_Add("", hwnd, ov) i ++ } if i = 0 { MsgBox, 48, %g_appTitle%, There are no currently running debuggable AutoHotkey scripts! return "" } LV_ModifyCol(1, "AutoHdr") LV_ModifyCol(2, "AutoHdr") Gui, Show, w640 h240, Select running script to debug global attSelection, attWin while !attSelection Sleep, 100 if attSelection = -1 filename := "", outwin := "", outpid := "" else { LV_GetText(filename, attSelection, 2) LV_GetText(outwin, attSelection, 1) WinGet, outpid, PID, ahk_id %outwin% } Gui, Destroy DetectHiddenWindows, %oldTM% SetTitleMatchMode, %oldTM% return filename } AttGuiClose: attSelection := -1 return AttGuiSelect: if A_GuiEvent != DoubleClick return attSelection := A_EventInfo return ;} ;{ Toolbar Commands F5:: if Dbg_OnBreak goto cmd_run else goto cmd_pause cmd_run: if !Dbg_OnBreak return SciTE_DeleteCurLineMarkers() DBGp_CmdRun(Dbg_Session) return cmd_pause: if !bIsAsync { MsgBox, 48, %g_appTitle%, Script pausing is not supported in this AutoHotkey version! return } ; We want to send AutoHotkey a break command. ; It must be sent asynchronously because we want to discard its ; response as fast as possible, otherwise OnBreak misbehaves due ; to the use of synchronous commands. Dbg_Session.Send("break", "", Func("DummyCallback")) return DummyCallback(session, ByRef response) { } cmd_stop: if bIsAttach { MsgBox, 35, %g_appTitle%, Do you wish to stop the script (YES) or just stop debugging (NO)? IfMsgBox, Cancel return IfMsgBox, No { Dbg_Session.property_set("-n A_DebuggerName --") Dbg_Session.detach() return } } ; Let the OnExit handler take care of this OnExit goto GuiClose F10:: cmd_stepinto: if !Dbg_OnBreak return SciTE_DeleteCurLineMarkers() DBGp_CmdStepInto(Dbg_Session) return F11:: cmd_stepover: if !Dbg_OnBreak return SciTE_DeleteCurLineMarkers() DBGp_CmdStepOver(Dbg_Session) return +F11:: cmd_stepout: if !Dbg_OnBreak return SciTE_DeleteCurLineMarkers() DBGp_CmdStepOut(Dbg_Session) return cmd_stacktrace: if Dbg_StackTraceWin return if !Dbg_OnBreak { if !bIsAsync return Dbg_GetStack() } ST_Create() return cmd_varlist: if Dbg_VarWin || (!Dbg_OnBreak && !bIsAsync) return VL_Create() return ;} ;{ SciTE Message Handler SciTEMsgHandler(wParam, lParam, msg, hwnd) { Critical global scintillahwnd, SciTEConnected, Dbg_ExitByDisconnect, Dbg_Session, Dbg_IsClosing, Dbg_WaitClose, Dbg_OnBreak, bIsAsync, bInitBk, InitBkList ; This code used to be a big if/else if block. I've changed it to this pseudo-switch structure. if IsLabel("_wP" wParam) goto _wP%wParam% else return false _wP0: ; SciTE handshake SciTEConnected := true return true _wP1: ; Breakpoint setting if !bIsAsync && !Dbg_OnBreak return true if !bInitBk { ; We need to launch the breakpoint setting code in a separate thread due to usage of COM global _temp := lParam + 1 ; convert line number from 0-based to 1-based SetTimer, SetBreakpointHelper, -10 } else InitBkList.Insert(lParam + 1) return true _wP2: ; Variable inspection if !bIsAsync && !Dbg_OnBreak { MsgBox, 48, %g_appTitle%, You can't inspect a variable whilst the script is running! return false } Dbg_VarName := Trim(StrGet(lParam, "UTF-8"), " `t`r`n=") ; Allow retrieving immediate children for object values SetEnableChildren(true) Dbg_Session.property_get("-n " Dbg_VarName, Dbg_Response) SetEnableChildren(false) dom := loadXML(Dbg_Response) Dbg_NewVarName := dom.selectSingleNode("/response/property/@name").text if Dbg_NewVarName = (invalid) { MsgBox, 48, %g_appTitle%, Invalid variable name: %Dbg_VarName% return false } if dom.selectSingleNode("/response/property/@type").text != "Object" { Dbg_VarIsReadOnly := dom.selectSingleNode("/response/property/@facet").text = "Builtin" Dbg_VarData := DBGp_Base64UTF8Decode(dom.selectSingleNode("/response/property").text) VE_Create(Dbg_VarName, Dbg_VarData, Dbg_VarIsReadOnly) }else OE_Create(dom) return true _wP3: ; Command static _ := [ "run", "stop", "pause", "stepinto", "stepover", "stepout", "stacktrace", "varlist" ] p := "cmd_" _[lParam] if IsLabel(p) gosub %p% return true _wP4: ; Hovering if !bIsAsync && !Dbg_OnBreak return true Dbg_VarName := Trim(StrGet(lParam, "UTF-8"), " `t`r`n") if Dbg_VarName = ToolTip else { Dbg_Session.property_get("-m 200 -n " Dbg_VarName, Dbg_Response) dom := loadXML(Dbg_Response) check := dom.selectSingleNode("/response/property/@name").text if check = (invalid) return true if dom.selectSingleNode("/response/property/@type").text != "Object" { Dbg_VarData := DBGp_Base64UTF8Decode(dom.selectSingleNode("/response/property").text) Dbg_VarSize := dom.selectSingleNode("/response/property/@size").text if Dbg_VarSize > 200 Dbg_VarData .= "..." ToolTip, %Dbg_VarName% = %Dbg_VarData% }else ToolTip, %Dbg_VarName% is an object } return true _wP5: ; Breakpoint initialization bInitBk := lParam if !bInitBk SetTimer, InitBreakpoints, -10 else InitBkList := [] return true _wP255: ; Disconnect if !Dbg_ExitByDisconnect { ; This code is executed if the debugger is still present rc := DBGp_CloseDebugger() if !rc ; cancel the deattach if we are in run mode return false ; tell SciTE to not unload the debugging features Dbg_IsClosing := true } Dbg_WaitClose := true ; the main thread can finish waiting now Sleep, 10 return true } SetEnableChildren(v) { global Dbg_Session if v { Dbg_Session.feature_set("-n max_children -v " dbgMaxChildren) Dbg_Session.feature_set("-n max_depth -v 1") }else { Dbg_Session.feature_set("-n max_children -v 0") Dbg_Session.feature_set("-n max_depth -v 0") } } SetBreakpointHelper: SetBreakpoint(_temp) return InitBreakpoints: for _, line in InitBkList SetBreakpoint(line) InitBkList := "" return SetBreakpoint(lParam) { global Dbg_Session, bInBkProcess uri := DBGp_EncodeFileURI(file := SciTE_GetFile()) bk := Util_GetBk(uri, lParam) if bk { Dbg_Session.breakpoint_remove("-d " bk.id) SciTE_BPSymbolRemove(lParam) Util_RemoveBk(uri, lParam) }else { bInBkProcess := true Dbg_Session.breakpoint_set("-t line -n " lParam " -f " uri, Dbg_Response) IfInString, Dbg_Response, 1<") ; Really nothing more to do } ; OnDebuggerBreak() - fired when we receive an asynchronous response from the debugger (including break responses). OnDebuggerBreak(session, ByRef response) { global Dbg_OnBreak, Dbg_Stack, Dbg_LocalContext, Dbg_GlobalContext, Dbg_VarWin, bInBkProcess, _tempResponse if bInBkProcess { ; A breakpoint was hit while the script running and the SciTE OnMessage thread is ; still running. In order to avoid crashing, we must delay this function's processing ; until the OnMessage thread is finished. _tempResponse := response SetTimer, TryHandlingBreakAgain, -100 return } dom := loadXML(response) ; load the XML document that the variable response is status := dom.selectSingleNode("/response/@status").text ; get the status if status = break { ; this is a break response SciTE_ToggleRunButton() Dbg_OnBreak := true ; set the Dbg_OnBreak variable ; Get info about the script currently running Dbg_GetStack() SciTE_UpdateCurLineOfCode() ST_Update() VL_Update() } } TryHandlingBreakAgain: OnDebuggerBreak(Dbg_Session, _tempResponse) return ; OnDebuggerStream() - fired when we receive a stream packet. OnDebuggerStream(session, ByRef stream) { dom := loadXML(stream) type := dom.selectSingleNode("/stream/@type").text data := DBGp_Base64UTF8Decode(dom.selectSingleNode("/stream").text) SP_Output(type, data) } ; OnDebuggerDisconnection() - fired when the debugger disconnects. OnDebuggerDisconnection(session) { global Critical Dbg_ExitByDisconnect := true ; tell our message handler to just return true without attempting to exit Dbg_ExitByGuiClose := true Dbg_IsClosing := true Dbg_OnBreak := true SendMessage, 1026, 0, 0,, ahk_id %scitehwnd% ; Disable [Debugging] mark in SciTE's window title } ;} ;{ Wrappers for DBGp Commands that set Dbg_OnBreak DBGp_CmdRun(a) { global ErrorLevel = 0 Dbg_OnBreak := false Dbg_HasStarted := true a.run() SciTE_ToggleRunButton() VE_Close() OE_Close() ST_Clear() } DBGp_CmdStepInto(a) { global ErrorLevel = 0 Dbg_OnBreak := false Dbg_HasStarted := true a.step_into() SciTE_ToggleRunButton() VE_Close() OE_Close() ST_Clear() } DBGp_CmdStepOver(a) { global ErrorLevel = 0 Dbg_OnBreak := false Dbg_HasStarted := true a.step_over() SciTE_ToggleRunButton() VE_Close() OE_Close() ST_Clear() } DBGp_CmdStepOut(a) { global ErrorLevel = 0 Dbg_OnBreak := false Dbg_HasStarted := true a.step_out() SciTE_ToggleRunButton() VE_Close() OE_Close() ST_Clear() } ;} ;{ Stacktrace Window ST_Create() { global ST_Destroy() Dbg_StackTraceWin := true Gui 2:+ToolWindow +AlwaysOnTop +LabelSTGui +Resize +MinSize -MaximizeBox Gui 2:Add, ListView, x0 y0 w320 h240 +NoSortHdr -LV0x10 gST_Go vST_ListView, Script filename|Line|Stack entry ST_Update() Gui 2:Show, w320 h240, Callstack } ST_Clear() { global if !Dbg_StackTraceWin return Gui 2:Default LV_Delete() } ST_Update() { global if !Dbg_StackTraceWin return aStackWhere := Util_UnpackNodes(Dbg_Stack.selectNodes("/response/stack/@where")) aStackFile := Util_UnpackNodes(Dbg_Stack.selectNodes("/response/stack/@filename")) aStackLine := Util_UnpackNodes(Dbg_Stack.selectNodes("/response/stack/@lineno")) Loop, % aStackFile.MaxIndex() aStackFile[A_Index] := DBGp_DecodeFileURI(aStackFile[A_Index]) Gui 2:Default LV_Delete() Loop, % aStackWhere.MaxIndex() LV_Add("", ST_ShortName(aStackFile[A_Index]), aStackLine[A_Index], aStackWhere[A_Index]) LV_ModifyCol(1, "AutoHdr") LV_ModifyCol(2, "AutoHdr") LV_ModifyCol(3, "AutoHdr") } ST_ShortName(a) { SplitPath, a, b return b } ST_Destroy() { global aStackWhere= aStackFile= aStackLine= Gui 2:Destroy Dbg_StackTraceWin := False } ST_Go: if A_GuiEvent != DoubleClick return SciTE_EnsureFileIsOpen(aStackFile[A_EventInfo]) SciTE_SetCurrentLine(aStackLine[A_EventInfo], 0) WinActivate, ahk_id %scitehwnd% return STGuiClose: ST_Destroy() return STGuiSize: GuiControl, Move, ST_ListView, w%A_GuiWidth% h%A_GuiHeight% return Dbg_GetStack() { global if !Dbg_OnBreak && !bIsAsync return Dbg_Session.stack_get("", Dbg_Stack := "") Dbg_Stack := loadXML(Dbg_Stack) } ;} ;{ Variable Inspection Window VE_Create(name, ByRef cont, readonly := 0) { global local VE_LF, VE_CRLF VE_Contents= if readonly { readonly = +Disabled readonly2 = +ReadOnly }else { readonly = readonly2 = } Gui 3:Destroy Gui 3:Default VE_BestChoice(VE_LF, VE_CRLF, cont) Gui 3:+ToolWindow +AlwaysOnTop +LabelVEGui +Resize +MinSize -MaximizeBox Gui 3:Add, Text, x8 y8 w80 h16 +Right, Variable name: Gui 3:Add, Text, x92 y8 w236 h16 +Border vVE_VarName, %name% Gui 3:Font, s9, %dbgTextFont% Gui 3:Add, Edit, x8 y32 w320 h240 vVE_Contents hwndVE_Cont_HWND +HScroll %readonly2%, % cont Gui 3:Font Gui 3:Add, Button, x94 y280 w80 h32 gVE_Update %readonly%, Update Gui 3:Add, Radio, x268 y280 w60 h16 Group vVE_LineEnd %VE_LF%, LF Gui 3:Add, Radio, x268 y296 w60 h16 %VE_CRLF%, CR+LF Gui 3:Show, w336 h320, Variable inspection ; Don't select all the text when the window is shown SendMessage, 0xB1, 0, 0,, ahk_id %VE_Cont_HWND% } VE_BestChoice(ByRef lf, ByRef crlf, ByRef a) { if !InStr(a, "`r`n") { lf = Checked crlf = }else { lf = crlf = Checked } } VE_Update: GuiControlGet, VE_VarName,, VE_VarName Gui, Submit VE_Close() if VE_LineEnd = 2 StringReplace, VE_Contents, VE_Contents, `n, `r`n, All Dbg_Session.property_set("-n " VE_VarName " -- " (VE_C2 := DBGp_Base64UTF8Encode(VE_Contents))) VarSetCapacity(VE_Contents, 0) VL_Update() if InStr(VE_VarName, ".") || InStr("VE_VarName", "[") OE_Update(VE_C2) VarSetCapacity(VE_C2, 0) return VEGuiClose: VE_Close() return VE_Close() { Gui 3:Destroy } VEGuiSize: VE_neww := A_GuiWidth - 16 VE_newh := A_GuiHeight - 80 VE_initx := A_GuiWidth - 68 VE_inity := VE_newh + 40 GuiControl, Move, VE_Contents, w%VE_neww% h%VE_newh% GuiControl, Move, Update, % "x" (8+Floor((A_GuiWidth-84)/2)-40) " y" VE_inity GuiControl, MoveDraw, LF, x%VE_initx% y%VE_inity% GuiControl, MoveDraw, CR+LF, % "x" VE_initx " y" (VE_inity+16) return ;} ;{ Variable List Window VL_Create() { global VL_Destroy() Dbg_VarWin := true Gui 4:+ToolWindow +AlwaysOnTop +LabelVLGui +Resize +MinSize -MaximizeBox Gui 4:Add, ListView, x0 y0 w320 h240 gVL_Inspect vVL_Listview, Scope|Variable name|Contents (partial) VL_Update() Gui 4:Show, w320 h240, Variable list } VL_Update() { global if !Dbg_VarWin return ; read ToolTip, Updating variable list... Dbg_GetContexts() VL_Local := Util_UnpackNodes(Dbg_LocalContext.selectNodes("/response/property/@name")) VL_Global := Util_UnpackNodes(Dbg_GlobalContext.selectNodes("/response/property/@name")) VL_NVars := VL_Local.MaxIndex() + VL_Global.MaxIndex() VL_LocalCont := Util_UnpackContNodes(Dbg_LocalContext.selectNodes("/response/property")) VL_GlobalCont := Util_UnpackContNodes(Dbg_GlobalContext.selectNodes("/response/property")) ; update Gui 4:Default LV_Delete() Loop, % VL_Local.MaxIndex() LV_Add("", "Local", VL_Local[A_Index], VL_LocalCont[A_Index]) Loop, % VL_Global.MaxIndex() LV_Add("", "Global", VL_Global[A_Index], VL_GlobalCont[A_Index]) ToolTip } VL_ShortCont(a) { if pos := InStr(a, "`n") a := Trim(SubStr(a, 1, pos-1), "`r") "..." if StrLen(a) = 65 a .= "..." return a } VL_Destroy() { global Gui 4:Destroy Dbg_VarWin := false } VL_Inspect: if !bIsAsync && !Dbg_OnBreak { MsgBox, 48, %g_appTitle%, You can't inspect a variable while the script is running! return } if A_GuiEvent != DoubleClick return LV_GetText(VL_Scope, A_EventInfo, 1) VL_Scope := VL_Scope != "Local" LV_GetText(VL_VarName, A_EventInfo, 2) SetEnableChildren(true) Dbg_Session.property_get("-c " VL_Scope " -n " VL_VarName, Dbg_Response) SetEnableChildren(false) dom := loadXML(Dbg_Response) if dom.selectSingleNode("/response/property/@type").text != "Object" { VL_VarIsReadOnly := dom.selectSingleNode("/response/property/@facet").text = "Builtin" VL_VarData := DBGp_Base64UTF8Decode(dom.selectSingleNode("/response/property").text) VE_Create(VL_VarName, VL_VarData, VL_VarIsReadOnly) }else OE_Create(dom) dom := "" return VLGuiClose: VL_Destroy() return VLGuiSize: GuiControl, Move, VL_Listview, w%A_GuiWidth% h%A_GuiHeight% return Dbg_GetContexts() { global if !bIsAsync && !Dbg_OnBreak return Dbg_Session.feature_set("-n max_data -v 65") Dbg_Session.context_get("-c 0", Dbg_LocalContext) Dbg_Session.context_get("-c 1", Dbg_GlobalContext) Dbg_Session.feature_set("-n max_data -v " dbgMaxData) Dbg_LocalContext := loadXML(Dbg_LocalContext) Dbg_GlobalContext := loadXML(Dbg_GlobalContext) } ;} ;{ Stream Window SP_Output(stream, data) { global Dbg_StreamWin, SP_Console, SP_ConHWND if !Dbg_StreamWin { Gui 5:Font, s9, %dbgTextFont% Gui 5:+ToolWindow +AlwaysOnTop +LabelSPGui +Resize +MinSize -MaximizeBox Gui 5:Add, Edit, x0 y0 w320 h240 +ReadOnly vSP_Console hwndSP_ConHWND Gui 5:Show, w320 h240, Stream viewer Dbg_StreamWin := true } GuiControlGet, ctext, 5:, SP_Console StringReplace, data, data, `r`n, `n, All IfNotInString, data, `n ctext .= "<" stream "> " data "`n" else ctext .= "<" stream ">:`n" data "`n" ctext := SubStr(ctext, -1023) ; Limit the output to 1 KB of data GuiControl 5:, SP_Console, % ctext SendMessage, 0xB6, 0, 999999,, ahk_id %SP_ConHWND% } SPGuiSize: GuiControl, Move, SP_Console, w%A_GuiWidth% h%A_GuiHeight% return SPGuiClose: Gui 5:Destroy Dbg_StreamWin := false return ;} ;{ Object Inspection Window OE_Create(ByRef objdom) { global local root OE_Data := {} Gui 6:Destroy Gui 6:Default Gui 6:+ToolWindow +AlwaysOnTop +LabelOEGui +Resize +MinSize -MaximizeBox Gui 6:Add, TreeView, x0 y0 w336 h320 vOE_Tree gOE_Event AltSubmit root := TV_Add(objdom.selectSingleNode("/response/property/@name").text) OE_Add(objdom.selectNodes("/response/property[1]/property"), root) TV_Modify(root, "Expand") Gui 6:Show, w336 h320, Object inspection } OE_Update(ByRef cont) { global OE_TempNode OE_TempNode.text := cont } OE_Add(nodes, tnode) { global OE_Data Loop, % nodes.length { node := nodes.item[A_Index-1] ttnode := TV_Add(node.attributes.getNamedItem("name").text, tnode) needToLoadChildren := node.attributes.getNamedItem("children").text fullName := node.attributes.getNamedItem("fullname").text nType := node.attributes.getNamedItem("type").text if needToLoadChildren q := TV_Add("{FAIL}", ttnode) OE_Data[ttnode] := { loadC: needToLoadChildren, name: fullName, type: nType, text: node.text, dummyC: q } } } OE_Preview(node) { ; TODO } OE_Close() { global OE_Data Gui 6:Destroy OE_Data := "" } OE_OnDoubleClick(itemId) { global OE_Data node := OE_Data[itemId] fullname := node.name if fullname && node.type != "object" { cont := DBGp_Base64UTF8Decode(node.text) VE_Create(fullname, cont) } } OE_OnExpand(itemId) { global OE_Data, Dbg_Session node := OE_Data[itemId] if !node.loadC return TV_Modify(A_EventInfo, "-Expand") SetEnableChildren(true) Dbg_Session.property_get("-n " node.name, Dbg_Response) SetEnableChildren(false) dom := loadXML(Dbg_Response) node.loadC := false OE_Add(dom.selectNodes("/response/property[1]/property"), itemId) TV_Delete(node.dummyC) TV_Modify(A_EventInfo, "+Expand") } OE_Event: if A_GuiEvent = + OE_OnExpand(A_EventInfo) else if A_GuiEvent = DoubleClick OE_OnDoubleClick(A_EventInfo) return OEGuiSize: GuiControl, Move, OE_Tree, w%A_GuiWidth% h%A_GuiHeight% return OEGuiClose: OE_Close() return ;} ;{ Even More SciTE Kitchen Sink Functions SciTE_UpdateCurLineOfCode() { global Dbg_Stack, szFilename cLine := Dbg_Stack.selectSingleNode("/response/stack[1]/@lineno").text cFNameURI := Dbg_Stack.selectSingleNode("/response/stack[1]/@filename").text cFName := DBGp_DecodeFileURI(cFNameURI) if cLine = { SciTE_EnsureFileIsOpen(szFilename) return } SciTE_EnsureFileIsOpen(cFName) SciTE_SetCurrentLine(cLine) } SciTE_RedrawLine(hwnd, line) { global IfWinNotActive, ahk_id %scitehwnd% WinActivate DllCall("SendMessage", "ptr", hwnd, "uint", SCI_ENSUREVISIBLEENFORCEPOLICY, "int", line, "int", 0) DllCall("SendMessage", "ptr", hwnd, "uint", SCI_GOTOLINE, "int", line, "int", 0) } SciTE_EnsureFileIsOpen(fname) { global oSciTE, scitehwnd if SciTE_GetFile() != fname oSciTE.OpenFile(fname) IfWinNotActive, ahk_id %scitehwnd% WinActivate, ahk_id %scitehwnd% } SciTE_GetFile() { global oSciTE return oSciTE.CurrentFile } SciTE_SetCurrentLine(line, mode := 1) ; show the current line markers in SciTE { global line-- if mode { ; Delete current markers SciTE_DeleteCurLineMarkers() ; Add markers DllCall("SendMessage", "ptr", scintillahwnd, "uint", SCI_MARKERADD, "int", line, "int", 11) DllCall("SendMessage", "ptr", scintillahwnd, "uint", SCI_MARKERADD, "int", line, "int", 12) } ; Refresh the Scintilla control SciTE_RedrawLine(scintillahwnd, line) } SciTE_DeleteCurLineMarkers() ; delete the current line markers in SciTE { global line-- ; Delete current markers DllCall("SendMessage", "ptr", scintillahwnd, "uint", SCI_MARKERDELETEALL, "int", 11, "int", 0) DllCall("SendMessage", "ptr", scintillahwnd, "uint", SCI_MARKERDELETEALL, "int", 12, "int", 0) } SciTE_BPSymbol(line) ; show the current line markers in SciTE { global line-- ; Add markers DllCall("SendMessage", "ptr", scintillahwnd, "uint", SCI_MARKERADD, "int", line, "int", 10) } SciTE_BPSymbolRemove(line) ; show the current line markers in SciTE { global line-- ; Add markers DllCall("SendMessage", "ptr", scintillahwnd, "uint", SCI_MARKERDELETE, "int", line, "int", 10) } ;} ;{ Sandbox Util_UnpackNodes(nodes) { o := [] Loop, % nodes.length o.Insert(nodes.item[A_Index-1].text) return o } Util_UnpackContNodes(nodes) { o := [] Loop, % nodes.length node := nodes.item[A_Index-1] ,o.Insert(node.attributes.getNamedItem("type").text != "object" ? VL_ShortCont(DBGp_Base64UTF8Decode(node.text)) : "(Object)") return o } Util_EscapeRegEx(str) { static tab := "\.*?+[{|()^$" Loop, % StrLen(tab) StringReplace, str, str, % SubStr(tab, A_Index, 1), % "\" SubStr(tab, A_Index, 1), All return str } Util_ProcessExist(a) { t := ErrorLevel Process, Exist, %a% r := ErrorLevel ErrorLevel := t return r } Util_AddBkToList(uri, line, id, cond := "") { global Dbg_BkList Dbg_BkList[uri, line] := { id: id, cond: cond } } Util_GetBk(uri, line) { global Dbg_BkList return Dbg_BkList[uri, line] } Util_RemoveBk(uri, line) { global Dbg_BkList Dbg_BkList[uri].Remove(line) } loadXML(ByRef data) { o := ComObjCreate("MSXML2.DOMDocument") o.async := false o.setProperty("SelectionLanguage", "XPath") o.loadXML(data) return o } GetExeMachine(exepath) { exe := FileOpen(exepath, "r") if !exe return exe.Seek(60), exe.Seek(exe.ReadUInt()+4) return exe.ReadUShort() } ObjJoin(obj, delim := " ", wrap := "") { var := "" for _,val in obj val := wrap val wrap, var .= A_Index>1 ? delim val : val return var } ;}