Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
#SingleInstance, Off
#NoEnv
SetBatchLines, -1
DefaultPath = C:\Windows\ShellNew\Template.ahk
DefaultName = %A_UserName%
DefaultDesc =
FGColor = 0xCDEDED
BGColor = 0x3F3F3F
TabSize = 4
Indent := "`t"
TypeFace = Consolas
Font = s9 wBold
Title = CodeQuickTester
Menu, Tray, Icon, %A_AhkPath%, 2
Shell := ComObjCreate("WScript.Shell")
FileEncoding, UTF-8
FilePath := %True% ? RegExReplace(%True%, "^ahk:") : DefaultPath
if FileExist(FilePath)
Code := FileOpen(FilePath, "r").Read()
else if (FilePath ~= "^https?://")
Code := UrlDownloadToVar(FilePath)
Menu, MenuBar, Add, &Save, SaveButton
Menu, MenuBar, Add, &Load, LoadButton
Menu, MenuBar, Add, &Fetch, FetchButton
Menu, MenuBar, Add, &Paste, PasteButton
Menu, MenuBar, Add, P&arams, ParamsButton
Menu, MenuBar, Add, Re&indent, IndentButton
Menu, MenuBar, Add, &Help, ^h
Menu, MenuBar, Add, Install, ServiceHandler
Gui, Main:New, +Resize +hWndhMainWindow
Gui, Menu, MenuBar
Gui, Margin, 5, 5
Gui, Font, %Font%, %TypeFace%
DllCall("LoadLibrary", "Str", "Msftedit.dll")
Gui, Add, Custom, ClassRichEdit50W hWndhCodeEditor vCodeEditor +0x5031b1c4 +E0x20000
InitRichEdit(hCodeEditor, BGColor, FGColor, TabSize)
Gui, Font, s8 w400 q0, Microsoft Sans Serif
Gui, Add, Button, gRunButton vRun, &Run
Gui, Add, StatusBar
SB_SetParts(70, 70, 70)
LoadCode(Code)
if (FilePath == DefaultPath)
SendMessage, 0x0B1, -1, -1,, ahk_id %hCodeEditor% ; EM_SETSEL
Gui, Show, w640 h480, %Title%
OnMessage(0x100, "ProcessEditEvents") ; WM_KEYDOWN
OnMessage(0x201, "ProcessEditEvents") ; WM_LBUTTONDOWN
OnMessage(0x202, "ProcessEditEvents") ; WM_LBUTTONUP
OnMessage(0x204, "ProcessEditEvents") ; WM_RBUTTONDOWN
return
#If Exec.Status == 0
*Escape::Exec.Terminate() ; CheckIfRunning updates the GUI
#If WinActive("ahk_id" hMainWindow)
*Escape::return
Tab::
ControlGet, Selected, Selected,,, ahk_id %hCodeEditor%
if (Selected == "")
SendMessage, 0xC2, 1, &(x:="`t"),, ahk_id %hCodeEditor% ; EM_REPLACESEL
UpdateStatusBar()
return
+Tab::return
^h::Run, %A_AhkPath%\..\AutoHotkey.chm
#If
InitRichEdit(hWnd, BGColor, FGColor, TabSize=4)
{
; Set background color
SendMessage, 0x443, 0, BGColor,, ahk_id %hWnd% ; EM_SETBKGNDCOLOR
; Set FG color
VarSetCapacity(CharFormat, 116, 0)
NumPut(116, CharFormat, 0, "UInt") ; cbSize := sizeOf(CHARFORMAT2)
NumPut(0x40000000, CharFormat, 4, "UInt") ; dwMask := CFM_COLOR
NumPut(FGColor, CharFormat, 20, "UInt") ; crTextColor := 0xBBGGRR
SendMessage, 0x444, 0, &CharFormat,, ahk_id %hWnd% ; EM_SETCHARFORMAT
; Set tab size to 4
VarSetCapacity(TabStops, 4, 0), NumPut(TabSize*4, TabStops, "UInt")
SendMessage, 0x0CB, 1, &TabStops,, ahk_id %hWnd% ; EM_SETTABSTOPS
; Change text limit from 32,767 to max
SendMessage, 0x435, 0, -1,, ahk_id %hWnd% ; EM_EXLIMITTEXT
; Disable inconsistent formatting
SendMessage, 0x4CC, 1, 1,, ahk_id %hWnd% ; EM_SETEDITSTYLE SES_EMULATESYSEDIT
}
/*
; Enable tab and other special keys
DllCall("Comctl32.dll\SetWindowSubclass", "Ptr", hCodeEditor, "Ptr", RegisterCallback("SubclassProc"), "Ptr", 0, "Ptr", 0)
SubclassProc(hWnd, Msg, wParam, lParam, IdSubclass, RefData)
{
If (Msg == 0x87) ; WM_GETDLGCODE
Return 0x4 ; DLGC_WANTALLCHARS
Return DllCall("Comctl32.dll\DefSubclassProc", "Ptr", hWnd, "UInt", Msg, "Ptr", wParam, "Ptr", lParam)
}
*/
ProcessEditEvents(wParam, lParam, Msg, hWnd)
{
global hCodeEditor
if (hWnd == hCodeEditor)
SetTimer, UpdateStatusBar, -0 ; Spawn new pseudo thread that will run after this function terminates
}
UpdateStatusBar()
{
global hCodeEditor
Gui, Main:Default
VarSetCapacity(GTL, 8, 0), NumPut(1200, GTL, 4, "UInt")
SendMessage, 0x45F, &GTL, 0,, ahk_id %hCodeEditor% ; EM_GETTEXTLENGTHEX (Handles newlines better than GuiControlGet on RE)
Len := ErrorLevel
ControlGet, Row, CurrentLine,,, ahk_id %hCodeEditor%
ControlGet, Col, CurrentCol,,, ahk_id %hCodeEditor%
SB_SetText("Len " Len, 1)
SB_SetText("Line " Row, 2)
SB_SetText("Col " Col, 3)
VarSetCapacity(s, 8, 0)
SendMessage, 0x0B0, &s+0, &s+4,, ahk_id %hCodeEditor% ; EM_GETSEL
Left := NumGet(s, 0, "UInt"), Right := NumGet(s, 4, "UInt")
Len := Right - Left - (Right > Len) ; > is a workaround for being able to select the end of the document with RE
SB_SetText(Len > 0 ? "Selection Length: " Len : "", 4) ; >0 because sometimes it comes up as -1 if you hold down paste
}
MainGuiClose:
GuiControlGet, CodeEditor
if !CodeEditor
ExitApp
MsgBox, 308, %Title%, Are you sure you want to exit?
IfMsgBox, Yes
ExitApp
return
MainGuiSize:
Critical
Process, Priority,, R
GuiControl, Move, CodeEditor, % "x" 5 "y" 5 "w" A_GuiWidth-10 "h" A_GuiHeight-60
ButtonWidth := (A_GuiWidth-10)
GuiControl, Move, Run, % "x" 5 "y" A_GuiHeight-50 "w" ButtonWidth "h" 22
SetTimer, Normal, -100
return
Normal:
Process, Priority,, N
return
MainGuiDropFiles:
LoadCode(FileOpen(StrSplit(A_GuiEvent, "`n")[1], "r").Read())
return
RunButton:
if (Exec.Status == 0) ; Running
Exec.Terminate() ; CheckIfRunning updates the GUI
else ; Not running or doesn't exist
{
GuiControlGet, CodeEditor, Main:
GuiControlGet, Params, Params:
Exec := ExecScript(CodeEditor, Params, DeHashBang(CodeEditor))
GuiControl, Main:, Run, &Kill
SetTimer, CheckIfRunning, 100
}
return
CheckIfRunning:
if (Exec.Status == 1)
{
SetTimer, CheckIfRunning, Off
GuiControl, Main:, Run, &Run
}
return
SaveButton:
Gui, +OwnDialogs
FileSelectFile, FilePath, S2
if ErrorLevel
return
GuiControlGet, CodeEditor
FileOpen(FilePath, "w").Write(CodeEditor)
return
LoadButton:
Gui, +OwnDialogs
FileSelectFile, FilePath, 3
if !ErrorLevel
LoadCode(FileOpen(FilePath, "r").Read())
return
FetchButton:
Gui, +OwnDialogs
InputBox, Url, %Title%, Enter a URL to fetch code from.
if (Url := Trim(Url))
LoadCode(UrlDownloadToVar(Url))
return
PasteButton:
Gui, Paste:New, +OwnerMain +ToolWindow
Gui, Margin, 5, 5
Gui, Font, s8, Microsoft Sans Serif
Gui, Add, Text, xm ym w30 h22 +0x200, Desc:
Gui, Add, Edit, x+5 yp w125 h22 vPasteDesc, %DefaultDesc%
Gui, Add, Button, x+4 yp-1 w52 h24 Default gPaste, Paste
Gui, Add, Text, xm y+5 w30 h22 +0x200, Name:
Gui, Add, Edit, x+5 yp w100 h22 vPasteName, %DefaultName%
Gui, Add, ComboBox, x+5 yp w75 vPasteChan hWndPasteChan, Announce||#ahk|#ahkscript
PostMessage, 0x153, -1, 22-6,, ahk_id %PasteChan% ; Set height of ComboBox
Gui, Show,, Paste
return
PasteGuiEscape:
Gui, Destroy
return
Paste:
Gui, Submit
Gui, Destroy
GuiControlGet, CodeEditor, Main:
Link := Ahkbin(CodeEditor, PasteName, PasteDesc, PasteChan)
MsgBox, 292, %Title%, Link acquired:`n%Link%`n`nCopy to clipboard?
IfMsgBox, Yes
Clipboard := Link
return
ParamsButton:
Gui, Params:New, +OwnerMain +ToolWindow
Gui, Margin, 5, 5
Gui, Font, s8, Microsoft Sans Serif
Gui, Add, Text, xm ym w300 h22 Center +0x200, Params will be cleared on window exit
Gui, Add, Edit, w300 h22 vParams
Gui, Show,, Command Line Params
return
ParamsGuiEscape:
Gui, Destroy
return
IndentButton:
GuiControlGet, CodeEditor, Main:
LoadCode(AutoIndent(CodeEditor, Indent))
return
ServiceHandler:
Gui, +OwnDialogs
if ServiceHandlerInstalled()
{
MsgBox, 36, , Are you sure you want to remove CodeQuickTester from being the default service handler for "ahk:" links?
IfMsgBox, Yes
RemoveServiceHandler()
}
else
{
MsgBox, 36, , Are you sure you want to install CodeQuickTester as the default service handler for "ahk:" links?
IfMsgBox, Yes
AddServiceHandler()
}
return
LoadCode(Code)
{
GuiControlGet, CodeEditor
if (CodeEditor && CodeEditor != Code)
{
MsgBox, 308, %Title%, Are you sure you want to overwrite your code?
IfMsgBox, No
return
}
GuiControl,, CodeEditor, %Code%
UpdateStatusBar()
}
UrlDownloadToVar(Url)
{
try
{
http := ComObjCreate("WinHttp.WinHttpRequest.5.1")
http.Open("GET", Url, false), http.Send()
return http.ResponseText
}
catch
throw Exception("Something went wrong with the HTTP Request. Invalid URL?")
}
; Modified from https://github.com/cocobelgica/AutoHotkey-Util/blob/master/ExecScript.ahk
ExecScript(Script, Params="", AhkPath="")
{
Name := "AHK_CQT_" A_TickCount
Pipe := []
Loop, 2
{
Pipe[A_Index] := DllCall("CreateNamedPipe"
, "Str", "\\.\pipe\" name
, "UInt", 2, "UInt", 0
, "UInt", 255, "UInt", 0
, "UInt", 0, "UPtr", 0
, "UPtr", 0, "UPtr")
}
if !FileExist(AhkPath)
AhkPath := A_AhkPath
Call = "%AhkPath%" /CP65001 "\\.\pipe\%Name%"
Shell := ComObjCreate("WScript.Shell")
Exec := Shell.Exec(Call " " Params)
DllCall("ConnectNamedPipe", "UPtr", Pipe[1], "UPtr", 0)
DllCall("CloseHandle", "UPtr", Pipe[1])
DllCall("ConnectNamedPipe", "UPtr", Pipe[2], "UPtr", 0)
FileOpen(Pipe[2], "h", "UTF-8").Write(Script)
DllCall("CloseHandle", "UPtr", Pipe[2])
return Exec
}
Ahkbin(Content, Name="", Desc="", Channel="")
{
static URL := "http://p.ahkscript.org/"
Form := "code=" UriEncode(Content)
if Name
Form .= "&name=" UriEncode(Name)
if Desc
Form .= "&desc=" UriEncode(Desc)
if Channel
Form .= "&announce=on&channel=" UriEncode(Channel)
Pbin := ComObjCreate("WinHttp.WinHttpRequest.5.1")
Pbin.Open("POST", URL, False)
Pbin.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
Pbin.Send(Form)
return Pbin.Option(1)
}
; Modified by GeekDude from http://goo.gl/0a0iJq
UriEncode(Uri, RE="[0-9A-Za-z]") {
VarSetCapacity(Var, StrPut(Uri, "UTF-8"), 0), StrPut(Uri, &Var, "UTF-8")
While Code := NumGet(Var, A_Index - 1, "UChar")
Res .= (Chr:=Chr(Code)) ~= RE ? Chr : Format("%{:02X}", Code)
Return, Res
}
DeHashBang(Script)
{
AhkPath := A_AhkPath
if RegExMatch(Script, "`a)^\s*`;#!\s*(.+)", Match)
{
AhkPath := Trim(Match1)
Vars := {"%A_ScriptDir%": A_WorkingDir
, "%A_WorkingDir%": A_WorkingDir
, "%A_AppData%": A_AppData
, "%A_AppDataCommon%": A_AppDataCommon
, "%A_LineFile%": A_ScriptFullPath
, "%A_AhkPath%": A_AhkPath
, "%A_AhkDir%": A_AhkPath "\.."}
for SearchText, Replacement in Vars
StringReplace, AhkPath, AhkPath, %SearchText%, %Replacement%, All
}
return AhkPath
}
AddServiceHandler()
{
RegWrite, REG_SZ, HKCU, Software\Classes\ahk,, URL:AHK Script Protocol
RegWrite, REG_SZ, HKCU, Software\Classes\ahk, URL Protocol
RegWrite, REG_SZ, HKCU, Software\Classes\ahk\shell\open\command,, "%A_AhkPath%" "%A_ScriptFullPath%" "`%1"
}
RemoveServiceHandler()
{
RegDelete, HKCU, Software\Classes\ahk
}
ServiceHandlerInstalled()
{
RegRead, Out, HKCU, Software\Classes\ahk
return !ErrorLevel
}
AutoIndent(Code, Indent = "`t", Newline = "`r`n")
{
IndentRegEx =
( LTrim Join
Catch|else|for|Finally|if|IfEqual|IfExist|
IfGreater|IfGreaterOrEqual|IfInString|
IfLess|IfLessOrEqual|IfMsgBox|IfNotEqual|
IfNotExist|IfNotInString|IfWinActive|IfWinExist|
IfWinNotActive|IfWinNotExist|Loop|Try|while
)
; Lock and Block are modified ByRef by Current
Lock := [], Block := []
ParentIndent := Braces := 0
ParentIndentObj := []
for each, Line in StrSplit(Code, "`n", "`r")
{
Text := Trim(RegExReplace(Line, "\s;.*")) ; Comment removal
First := SubStr(Text, 1, 1), Last := SubStr(Text, 0, 1)
FirstTwo := SubStr(Text, 1, 2)
IsExpCont := (Text ~= "i)^\s*(&&|OR|AND|\.|\,|\|\||:|\?)")
IndentCheck := (Text ~= "iA)}?\s*\b(" IndentRegEx ")\b")
if (First == "(" && Last != ")")
Skip := True
if (Skip)
{
if (First == ")")
Skip := False
Out .= Newline . RTrim(Line)
continue
}
if (FirstTwo == "*/")
Block := [], ParentIndent := 0
if Block.MinIndex()
Current := Block, Cur := 1
else
Current := Lock, Cur := 0
; Round converts "" to 0
Braces := Round(Current[Current.MaxIndex()].Braces)
ParentIndent := Round(ParentIndentObj[Cur])
if (First == "}")
{
while ((Found := SubStr(Text, A_Index, 1)) ~= "}|\s")
{
if (Found ~= "\s")
continue
if (Cur && Current.MaxIndex() <= 1)
break
Special := Current.Pop().Ind, Braces--
}
}
if (First == "{" && ParentIndent)
ParentIndent--
Out .= Newline
Loop, % Special ? Special-1 : Round(Current[Current.MaxIndex()].Ind) + Round(ParentIndent)
Out .= Indent
Out .= Trim(Line)
if (FirstTwo == "/*")
{
if (!Block.MinIndex())
{
Block.Push({ParentIndent: ParentIndent
, Ind: Round(Lock[Lock.MaxIndex()].Ind) + 1
, Braces: Round(Lock[Lock.MaxIndex()].Braces) + 1})
}
Current := Block, ParentIndent := 0
}
if (Last == "{")
{
Braces++, ParentIndent := (IsExpCont && Last == "{") ? ParentIndent-1 : ParentIndent
Current.Push({Braces: Braces
, Ind: ParentIndent + Round(Current[Current.MaxIndex()].ParentIndent) + Braces
, ParentIndent: ParentIndent + Round(Current[Current.MaxIndex()].ParentIndent)})
ParentIndent := 0
}
if ((ParentIndent || IsExpCont || IndentCheck) && (IndentCheck && Last != "{"))
ParentIndent++
if (ParentIndent > 0 && !(IsExpCont || IndentCheck))
ParentIndent := 0
ParentIndentObj[Cur] := ParentIndent
Special := 0
}
if Braces
throw Exception("Segment Open!")
return SubStr(Out, StrLen(Newline)+1)
}
#SingleInstance, Off
#NoEnv
SetBatchLines, -1
global B_Params := []
Loop, %0%
B_Params.Push(%A_Index%)
Menu, Tray, Icon, %A_AhkPath%, 2
FileEncoding, UTF-8
; TODO: command line input
; TODO: Figure out why it gets sometimes gets stuck on "Kill" when using MultiTester
Tester := new CodeQuickTester()
Tester.RegisterCloseCallback(Func("TesterClose"))
return
#If Tester.Exec.Status == 0 ; Running
~*Escape::Tester.Exec.Terminate()
#If
TesterClose(Tester)
{
ExitApp
}
/* MultiTester
ScriptPID := DllCall("GetCurrentProcessId")
Testers := []
Testers[new CodeQuickTester()] := True
return
#If WinActive("ahk_pid" ScriptPID)
^n::Testers[new CodeQuickTester()] := True
#If
~*Escape::
for Tester in Testers
Tester.Exec.Terminate()
return
*/
class CodeQuickTester
{
static Msftedit := DllCall("LoadLibrary", "Str", "Msftedit.dll")
DefaultPath := "C:\Windows\ShellNew\Template.ahk"
Title := "CodeQuickTester"
__New()
{
; TODO: Settings passed in
this.DefaultName := "GeekDude"
this.DefaultDesc := ""
this.FGColor := 0xCDEDED
this.BGColor := 0x3F3F3F
this.TabSize := 4
this.Indent := "`t"
this.TypeFace := "Microsoft Sans Serif"
this.Font := "s8 wNorm"
this.CodeTypeFace := "Consolas"
this.CodeFont := "s9 wBold"
this.Shell := ComObjCreate("WScript.Shell")
this.Bound := []
this.Bound.RunButton := this.RunButton.Bind(this)
this.Bound.OnMessage := this.OnMessage.Bind(this)
this.Bound.UpdateStatusBar := this.UpdateStatusBar.Bind(this)
this.Bound.CheckIfRunning := this.CheckIfRunning.Bind(this)
Buttons := new this.MenuButtons(this)
Menus :=
( Join
[
["&File", [
["&Save`tCtrl+S", Buttons.Save.Bind(Buttons)],
["&Open`tCtrl+O", Buttons.Open.Bind(Buttons)],
["&New`tCtrl+N", Buttons.New.Bind(Buttons)],
["&Fetch", Buttons.Fetch.Bind(Buttons)]
]], ["&Tools", [
["&Paste`tCtrl+P", Buttons.Paste.Bind(Buttons)],
["Re&indent`tCtrl+I", Buttons.Indent.Bind(Buttons)],
["Parameters", Buttons.Params.Bind(Buttons)],
["Install", Buttons.Install.Bind(Buttons)]
]], ["&Help", [
["Open &Help File`tCtrl+H", Buttons.Help.Bind(Buttons)],
["&About", Buttons.Help.Bind(Buttons)]
]]
]
)
Gui, New, +Resize +hWndhMainWindow
this.hMainWindow := hMainWindow
this.Menus := this.CreateMenuBar(Menus)
Gui, Menu, % this.Menus[1]
Gui, Margin, 5, 5
; Add code editor
Gui, Font, % this.CodeFont, % this.CodeTypeFace
this.InitRichEdit()
Gui, Font, % this.Font, % this.TypeFace
; Get starting tester contents
FilePath := B_Params[1] ? RegExReplace(B_Params[1], "^ahk:") : this.DefaultPath
this.Code := FileExist(FilePath) ? FileOpen(FilePath, "r").Read() : UrlDownloadToVar(FilePath)
if (FilePath == this.DefaultPath)
SendMessage, 0x0B1, -1, -1,, % "ahk_id" this.hCodeEditor ; EM_SETSEL bottom of document
; Add run button
Gui, Add, Button, hWndhRunButton, &Run
this.hRunButton := hRunButton
BoundFunc := this.Bound.RunButton
GuiControl, +g, %hRunButton%, %BoundFunc%
; Add status bar
Gui, Add, StatusBar
SB_SetParts(70, 70, 70)
this.UpdateStatusBar()
; Register for events
WinEvents.Register(this.hMainWindow, this)
for each, Msg in [0x100, 0x201, 0x202, 0x204] ; WM_KEYDOWN, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN
OnMessage(Msg, this.Bound.OnMessage)
Gui, Show, w640 h480, % this.Title
}
InitRichEdit()
{
Gui, Add, Custom, ClassRichEdit50W hWndhCodeEditor +0x5031b1c4 +E0x20000
this.hCodeEditor := hCodeEditor
; Set background color
SendMessage, 0x443, 0, this.BGColor,, ahk_id %hCodeEditor% ; EM_SETBKGNDCOLOR
; Set FG color
VarSetCapacity(CharFormat, 116, 0)
NumPut(116, CharFormat, 0, "UInt") ; cbSize := sizeOf(CHARFORMAT2)
NumPut(0x40000000, CharFormat, 4, "UInt") ; dwMask := CFM_COLOR
NumPut(this.FGColor, CharFormat, 20, "UInt") ; crTextColor := 0xBBGGRR
SendMessage, 0x444, 0, &CharFormat,, ahk_id %hCodeEditor% ; EM_SETCHARFORMAT
; Set tab size to 4
VarSetCapacity(TabStops, 4, 0), NumPut(this.TabSize*4, TabStops, "UInt")
SendMessage, 0x0CB, 1, &TabStops,, ahk_id %hCodeEditor% ; EM_SETTABSTOPS
; Change text limit from 32,767 to max
SendMessage, 0x435, 0, -1,, ahk_id %hCodeEditor% ; EM_EXLIMITTEXT
; Disable inconsistent formatting
SendMessage, 0x4CC, 1, 1,, ahk_id %hCodeEditor% ; EM_SETEDITSTYLE SES_EMULATESYSEDIT
}
Code[]
{
get {
GuiControlGet, CodeEditor,, % this.hCodeEditor
return CodeEditor
}
set {
GuiControl,, % this.hCodeEditor, %Value%
return Value
}
}
CreateMenuBar(Menu)
{
static MenuName := 0
Menus := ["CQT_" MenuName++]
for each, Item in Menu
{
Ref := Item[2]
if IsObject(Ref) && Ref._NewEnum()
{
SubMenus := this.CreateMenuBar(Ref)
Menus.Push(SubMenus*), Ref := ":" SubMenus[1]
}
Menu, % Menus[1], Add, % Item[1], %Ref%
}
return Menus
}
RunButton()
{
if (this.Exec.Status == 0) ; Running
this.Exec.Terminate() ; CheckIfRunning updates the GUI
else ; Not running or doesn't exist
{
; GuiControlGet, Params, Params:
Code := this.Code ; A temp var to avoid duplication of GuiControlGet
this.Exec := ExecScript(Code, "", DeHashBang(Code)) ; TODO: Implement Params
GuiControl,, % this.hRunButton, &Kill
SetTimer(this.Bound.CheckIfRunning, 100)
}
}
CheckIfRunning()
{
if (this.Exec.Status == 1)
{
SetTimer(this.Bound.CheckIfRunning, "Delete")
GuiControl,, % this.hRunButton, &Run
}
}
LoadCode(Code)
{
GuiControlGet, CodeEditor,, % this.hCodeEditor
if (CodeEditor && CodeEditor != Code) ; TODO: Do I need to Trim() here?
{
MsgBox, 308, %Title%, Are you sure you want to overwrite your code?
IfMsgBox, No
return
}
this.Code := Code, this.UpdateStatusBar()
}
OnMessage(wParam, lParam, Msg, hWnd)
{
if (hWnd == this.hCodeEditor)
{
if (Msg == 0x100 && Chr(wParam) == "`t")
{
ControlGet, Selected, Selected,,, % "ahk_id" this.hCodeEditor
if (Selected == "")
SendMessage, 0xC2, 1, &(x:="`t"),, % "ahk_id" this.hCodeEditor ; EM_REPLACESEL
this.UpdateStatusBar()
return False
}
; Call UpdateStatusBar after the edit handles the keystroke
SetTimer(this.Bound.UpdateStatusBar, -0)
return
}
}
UpdateStatusBar()
{
; Delete the timer if it was called by one
SetTimer(this.Bound.UpdateStatusBar, "Delete")
hCodeEditor := this.hCodeEditor
hMainWindow := this.hMainWindow
Gui, %hMainWindow%:Default
VarSetCapacity(GTL, 8, 0), NumPut(1200, GTL, 4, "UInt")
SendMessage, 0x45F, &GTL, 0,, ahk_id %hCodeEditor% ; EM_GETTEXTLENGTHEX (Handles newlines better than GuiControlGet on RE)
Len := ErrorLevel
ControlGet, Row, CurrentLine,,, ahk_id %hCodeEditor%
ControlGet, Col, CurrentCol,,, ahk_id %hCodeEditor%
SB_SetText("Len " Len, 1)
SB_SetText("Line " Row, 2)
SB_SetText("Col " Col, 3)
VarSetCapacity(s, 8, 0)
SendMessage, 0x0B0, &s+0, &s+4,, ahk_id %hCodeEditor% ; EM_GETSEL
Left := NumGet(s, 0, "UInt"), Right := NumGet(s, 4, "UInt")
Len := Right - Left - (Right > Len) ; > is a workaround for being able to select the end of the document with RE
SB_SetText(Len > 0 ? "Selection Length: " Len : "", 4) ; >0 because sometimes it comes up as -1 if you hold down paste
}
RegisterCloseCallback(CloseCallback)
{
this.CloseCallback := CloseCallback
}
GuiSize()
{
GuiControl, Move, % this.hCodeEditor, % "x" 5 "y" 5 "w" A_GuiWidth-10 "h" A_GuiHeight-60
GuiControl, Move, % this.hRunButton, % "x" 5 "y" A_GuiHeight-50 "w" A_GuiWidth-10 "h" 22
}
GuiClose()
{
; TODO: Finish auto-script-kill
if (this.Exec.Status == 0) ; Runnning
{
SetTimer(this.Bound.CheckIfRunning, "Delete")
this.Exec.Terminate()
}
; Relase wm_message hooks
for each, Msg in [0x100, 0x201, 0x202, 0x204] ; WM_KEYDOWN, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN
OnMessage(Msg, this.Bound.OnMessage, 0)
; Break all the BoundFunc circular references
this.Delete("Bound")
; Release WinEvents handler
WinEvents.Unregister(this.hMainWindow)
; Relase GUI window and control glabels
GuiControl, -g, % this.hRunButton ; TODO: Remove once -g bug is fixed
Gui, Destroy
; Release menu bar (Has to be done after Gui, Destroy)
for each, MenuName in this.Menus
Menu, %MenuName%, DeleteAll
this.CloseCallback()
}
class Paste
{
__New(Parent)
{
this.Parent := Parent
ParentWnd := this.Parent.hMainWindow
Gui, New, +Owner%ParentWnd% +ToolWindow +hWndhWnd
this.hWnd := hWnd
Gui, Margin, 5, 5
Gui, Font, % this.Parent.Font, % this.Parent.TypeFace
Gui, Add, Text, xm ym w30 h22 +0x200, Desc: ; 0x200 for vcenter
Gui, Add, Edit, x+5 yp w125 h22 hWndhPasteDesc, % this.Parent.DefaultDesc
this.hPasteDesc := hPasteDesc
Gui, Add, Button, x+4 yp-1 w52 h24 Default hWndhPasteButton, Paste
this.hPasteButton := hPasteButton
BoundPaste := this.Paste.Bind(this)
GuiControl, +g, %hPasteButton%, %BoundPaste%
Gui, Add, Text, xm y+5 w30 h22 +0x200, Name: ; 0x200 for vcenter
Gui, Add, Edit, x+5 yp w100 h22 hWndhPasteName, % this.Parent.DefaultName
this.hPasteName := hPasteName
Gui, Add, ComboBox, x+5 yp w75 hWndhPasteChan, Announce||#ahk|#ahkscript
this.hPasteChan := hPasteChan
PostMessage, 0x153, -1, 22-6,, ahk_id %hPasteChan% ; Set height of ComboBox
Gui, Show,, Paste
WinEvents.Register(this.hWnd, this)
}
GuiClose()
{
GuiControl, -g, % this.hPasteButton
WinEvents.Unregister(this.hWnd)
Gui, Destroy
}
Paste()
{
GuiControlGet, PasteDesc,, % this.hPasteDesc
GuiControlGet, PasteName,, % this.hPasteName
GuiControlGet, PasteChan,, % this.hPasteChan
this.GuiClose()
Link := Ahkbin(this.Parent.Code, PasteName, PasteDesc, PasteChan)
MsgBox, 292, %Title%, Link received:`n%Link%`n`nCopy to clipboard?
IfMsgBox, Yes
Clipboard := Link
}
}
class MenuButtons
{
__New(Parent)
{
this.Parent := Parent
}
Save()
{
Gui, +OwnDialogs
FileSelectFile, FilePath, S2
if ErrorLevel
return
GuiControlGet, CodeEditor,, % this.Parent.hCodeEditor
; TODO: Confirm before overwrite
FileOpen(FilePath, "w").Write(CodeEditor)
}
Open()
{
Gui, +OwnDialogs
FileSelectFile, FilePath, 3
if !ErrorLevel
this.Parent.LoadCode(FileOpen(FilePath, "r").Read())
}
New() ; TODO: Make this work for MultiTester mode
{
Run, %A_AhkPath% %A_ScriptFullPath%
}
Fetch()
{
Gui, +OwnDialogs
InputBox, Url, %Title%, Enter a URL to fetch code from.
if (Url := Trim(Url))
this.Parent.LoadCode(UrlDownloadToVar(Url))
}
Paste()
{ ; TODO: Recycle PasteInstance
if WinExist("ahk_id" this.PasteInstance.hWnd)
WinActivate, % "ahk_id" this.PasteInstance.hWnd
else
this.PasteInstance := new this.Parent.Paste(this.Parent)
}
Params()
{
; TODO
}
Indent()
{
this.Parent.LoadCode(AutoIndent(this.Parent.Code, this.Parent.Indent))
}
Help()
{
Run, %A_AhkPath%\..\AutoHotkey.chm
}
About()
{
; TODO
}
Install()
{
Gui, +OwnDialogs
if ServiceHandler.Installed()
{
MsgBox, 36, , Are you sure you want to remove CodeQuickTester from being the default service handler for "ahk:" links?
IfMsgBox, Yes
ServiceHandler.Remove()
}
else
{
MsgBox, 36, , Are you sure you want to install CodeQuickTester as the default service handler for "ahk:" links?
IfMsgBox, Yes
ServiceHandler.Install()
}
}
}
}
class ServiceHandler ; static class
{
static Protocol := "ahk"
Install()
{
Protocol := this.Protocol
RegWrite, REG_SZ, HKCU, Software\Classes\%Protocol%,, URL:AHK Script Protocol
RegWrite, REG_SZ, HKCU, Software\Classes\%Protocol%, URL Protocol
RegWrite, REG_SZ, HKCU, Software\Classes\%Protocol%\shell\open\command,, "%A_AhkPath%" "%A_ScriptFullPath%" "`%1"
}
Remove()
{
Protocol := this.Protocol
RegDelete, HKCU, Software\Classes\%Protocol%
}
Installed()
{
Protocol := this.Protocol
RegRead, Out, HKCU, Software\Classes\%Protocol%
return !ErrorLevel
}
}
class WinEvents ; static class
{
static Table := {}
Register(hWnd, Class, Prefix="Gui")
{
Gui, +LabelWinEvents.
this.Table[hWnd] := {Class: Class, Prefix: Prefix}
}
Unregister(hWnd)
{
this.Table.Delete(hWnd)
}
Dispatch(hWnd, Type)
{
Info := this.Table[hWnd]
return Info.Class[Info.Prefix . Type].Call(Info.Class)
}
; These *CANNOT* be added dynamically or handled dynamically via __Call
Close()
{
return WinEvents.Dispatch(this, "Close")
}
Escape()
{
return WinEvents.Dispatch(this, "Escape")
}
Size()
{
return WinEvents.Dispatch(this, "Size")
}
ContextMenu()
{
return WinEvents.Dispatch(this, "ContextMenu")
}
DropFiles()
{
return WinEvents.Dispatch(this, "DropFiles")
}
}
AutoIndent(Code, Indent = "`t", Newline = "`r`n")
{
IndentRegEx =
( LTrim Join
Catch|else|for|Finally|if|IfEqual|IfExist|
IfGreater|IfGreaterOrEqual|IfInString|
IfLess|IfLessOrEqual|IfMsgBox|IfNotEqual|
IfNotExist|IfNotInString|IfWinActive|IfWinExist|
IfWinNotActive|IfWinNotExist|Loop|Try|while
)
; Lock and Block are modified ByRef by Current
Lock := [], Block := []
ParentIndent := Braces := 0
ParentIndentObj := []
for each, Line in StrSplit(Code, "`n", "`r")
{
Text := Trim(RegExReplace(Line, "\s;.*")) ; Comment removal
First := SubStr(Text, 1, 1), Last := SubStr(Text, 0, 1)
FirstTwo := SubStr(Text, 1, 2)
IsExpCont := (Text ~= "i)^\s*(&&|OR|AND|\.|\,|\|\||:|\?)")
IndentCheck := (Text ~= "iA)}?\s*\b(" IndentRegEx ")\b")
if (First == "(" && Last != ")")
Skip := True
if (Skip)
{
if (First == ")")
Skip := False
Out .= Newline . RTrim(Line)
continue
}
if (FirstTwo == "*/")
Block := [], ParentIndent := 0
if Block.MinIndex()
Current := Block, Cur := 1
else
Current := Lock, Cur := 0
; Round converts "" to 0
Braces := Round(Current[Current.MaxIndex()].Braces)
ParentIndent := Round(ParentIndentObj[Cur])
if (First == "}")
{
while ((Found := SubStr(Text, A_Index, 1)) ~= "}|\s")
{
if (Found ~= "\s")
continue
if (Cur && Current.MaxIndex() <= 1)
break
Special := Current.Pop().Ind, Braces--
}
}
if (First == "{" && ParentIndent)
ParentIndent--
Out .= Newline
Loop, % Special ? Special-1 : Round(Current[Current.MaxIndex()].Ind) + Round(ParentIndent)
Out .= Indent
Out .= Trim(Line)
if (FirstTwo == "/*")
{
if (!Block.MinIndex())
{
Block.Push({ParentIndent: ParentIndent
, Ind: Round(Lock[Lock.MaxIndex()].Ind) + 1
, Braces: Round(Lock[Lock.MaxIndex()].Braces) + 1})
}
Current := Block, ParentIndent := 0
}
if (Last == "{")
{
Braces++, ParentIndent := (IsExpCont && Last == "{") ? ParentIndent-1 : ParentIndent
Current.Push({Braces: Braces
, Ind: ParentIndent + Round(Current[Current.MaxIndex()].ParentIndent) + Braces
, ParentIndent: ParentIndent + Round(Current[Current.MaxIndex()].ParentIndent)})
ParentIndent := 0
}
if ((ParentIndent || IsExpCont || IndentCheck) && (IndentCheck && Last != "{"))
ParentIndent++
if (ParentIndent > 0 && !(IsExpCont || IndentCheck))
ParentIndent := 0
ParentIndentObj[Cur] := ParentIndent
Special := 0
}
if Braces
throw Exception("Segment Open!")
return SubStr(Out, StrLen(Newline)+1)
}
; Modified from https://github.com/cocobelgica/AutoHotkey-Util/blob/master/ExecScript.ahk
ExecScript(Script, Params="", AhkPath="")
{
Name := "AHK_CQT_" A_TickCount
Pipe := []
Loop, 2
{
Pipe[A_Index] := DllCall("CreateNamedPipe"
, "Str", "\\.\pipe\" name
, "UInt", 2, "UInt", 0
, "UInt", 255, "UInt", 0
, "UInt", 0, "UPtr", 0
, "UPtr", 0, "UPtr")
}
if !FileExist(AhkPath)
throw Exception("AutoHotkey runtime not found: " AhkPath)
Call = "%AhkPath%" /CP65001 "\\.\pipe\%Name%"
Shell := ComObjCreate("WScript.Shell")
Exec := Shell.Exec(Call " " Params)
DllCall("ConnectNamedPipe", "UPtr", Pipe[1], "UPtr", 0)
DllCall("CloseHandle", "UPtr", Pipe[1])
DllCall("ConnectNamedPipe", "UPtr", Pipe[2], "UPtr", 0)
FileOpen(Pipe[2], "h", "UTF-8").Write(Script)
DllCall("CloseHandle", "UPtr", Pipe[2])
return Exec
}
DeHashBang(Script)
{
AhkPath := A_AhkPath
if RegExMatch(Script, "`a)^\s*`;#!\s*(.+)", Match)
{
AhkPath := Trim(Match1)
Vars := {"%A_ScriptDir%": A_WorkingDir
, "%A_WorkingDir%": A_WorkingDir
, "%A_AppData%": A_AppData
, "%A_AppDataCommon%": A_AppDataCommon
, "%A_LineFile%": A_ScriptFullPath
, "%A_AhkPath%": A_AhkPath
, "%A_AhkDir%": A_AhkPath "\.."}
for SearchText, Replacement in Vars
StringReplace, AhkPath, AhkPath, %SearchText%, %Replacement%, All
}
return AhkPath
}
UrlDownloadToVar(Url)
{
http := ComObjCreate("WinHttp.WinHttpRequest.5.1")
http.Open("GET", Url, false), http.Send()
return http.ResponseText
}
; Helper function, to make passing in expressions resulting in function objects easier
SetTimer(Label, Period)
{
SetTimer, %Label%, %Period%
}
SendMessage(Msg, wParam, lParam, hWnd)
{
; DllCall("SendMessage", "UPtr", hWnd, "UInt", Msg, "UPtr", wParam, "Ptr", lParam, "UPtr")
SendMessage, Msg, wParam, lParam,, ahk_id %hWnd%
}
Ahkbin(Content, Name="", Desc="", Channel="")
{
static URL := "http://p.ahkscript.org/"
Form := "code=" UriEncode(Content)
if Name
Form .= "&name=" UriEncode(Name)
if Desc
Form .= "&desc=" UriEncode(Desc)
if Channel
Form .= "&announce=on&channel=" UriEncode(Channel)
Pbin := ComObjCreate("WinHttp.WinHttpRequest.5.1")
Pbin.Open("POST", URL, False)
Pbin.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
Pbin.Send(Form)
return Pbin.Option(1)
}
; Modified by GeekDude from http://goo.gl/0a0iJq
UriEncode(Uri, RE="[0-9A-Za-z]") {
VarSetCapacity(Var, StrPut(Uri, "UTF-8"), 0), StrPut(Uri, &Var, "UTF-8")
While Code := NumGet(Var, A_Index - 1, "UChar")
Res .= (Chr:=Chr(Code)) ~= RE ? Chr : Format("%{:02X}", Code)
Return, Res
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment