[Editor] CodeQuickTester - Write and run code without saving to a temporary file

Post your working scripts, libraries and tools for AHK v1.1 and older
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

24 Nov 2018, 14:23

hotkeyguy wrote:
09 Nov 2018, 15:42
Hello GeekDude,

many thanks for CodeQuickTester v2.8, especially Save/Save As and rename. I just wanted to ask. And OutputDebug works now (not in v2.7). Yep, I got v2.7 compiled meanwhile. No problems starting v2.8.

Greetings and thanks for that early St. Nicholas' Day gift :xmas:
hotkeyguy
You're welcome! I'm not sure what you are trying to do with OutputDebug but if it's working now then great! I'm not aware of any changes I've made that would influence this, other than I updated the version of AHK_H being used in the compiled script.
CyL0N wrote: Sweet :thumbup: , but RIBBONS,where be the RIBBONS :shifty: .... i guess i Can wait :cookie: . But really though,much needed update so thanks. :salute:

A suggestion for the next patch, for status bar instead of , I think |, is a bit more poignant,comma just looks a little weird.
Something like this,

Code: Select all

 SB_SetText("Len " Len " | Line " Row " | Col " Col
 . (Sel > 0 ? " | Sel " Sel : "") " " this.SyntaxTip)
You may have confused ribbon design with the Windows 10 window manager. I haven't done any significant theming changes other than reducing/removing margins in the main GUI. It may be possible to grab one of those window theming dlls that are basically drop-in addons for the script but I can't do that while maintaining the "all functionality should be available when using just one file" requirement I've been trying to uphold.

As far as the status bar, I was mostly trying to copy how they handle the status bar in the popular new editor Visual Studio Code. A vertical pipe might be better, but maybe not. Microsoft has a reasonable design team and I would bet they've done UX testing on this topic and found people preferred the commas. Status bar design is definitely a point to revisit in the future.
SpecialGuest wrote: Keep it comin' GeekDude 8-)
By the way: I would love to see this get tabs some day
I'm glad you like what I've been cooking up! I would also like to see it get tabs some day, but I think there might need to be some rethinking first. The more parts in a single script, the more likely that script is to hang or crash. If I am going to increase the propensity for crashing, I should consider what the script should do in the event of a crash: Should I introduce auto-saving to a temporary file for unsaved script panes? That's a little antithetical to one of the goals of the script (don't create temporary files). Is that goal still a good thing to shoot for?

Should I attempt to go for a browser-like approach where each tab has its own process, so that if one crashes it won't take down the whole editor? If so, how should I perform process management? There's an API in Windows to designate some processes as children to be killed when the parent dies but when I was testing it I couldn't get it to work.

So many options :shock:
r2997790 wrote: Geekdude, thank you for your continuing hard work on CodeQuickTester. It's a wonderful tool.

I'm glad you haven't overloaded it with functionality. It remains a joy to use for testing code snippets and short routines.
Thank you for the nice comment!

I'm not sure what the threshold is for overloaded haha, I've been slowly adding more things to it over a long time. Working in it before there was a right click menu or a find and replace dialog just felt kind of... incomplete. I'm probably going to keep adding things until you change your mind :lol:.
SpecialGuest wrote:Desperate attempt at fixing integration (to be ignored lol) :
*snip*
ozzii wrote: @SpecialGuest
Thanks for the update but I have this when I click to open on your code:
*snip*
SpecialGuest wrote: Thanks for letting me know. Hadn't really had the chance to look at it very long last night.
Just saw the regular integration wasn't doing anything and attempted to fix the link.
It seems I didn't look at it long enough to notice the result being the whole page instead of the codebox' content :shh: lol
Consider this to be broken for now as we require the download link, which isn't available anymore on the current codebox..
With the download link you can retrieve the codebox' content but I don't see a way to do it with the forum being as it is atm.
ozzii wrote:OK, I will wait for a fix, I hope....
I've been thinking of a way to make this work, and I'm wondering now if there's a way for me to jam the code into something like a Base64 string that I pass to the script instead of an http(s) link. Of course, this will depend on how much data is supported in one of those links but people use similar links to embed images into html so hey maybe it'll work :)
kiwichick wrote: Hi there, this is such a nifty little script. So helpful! I have a couple of requests. First, will you please make it possible to open it maximised, or at least remember the last window setting? And second, will you please change the wording for "Open Help File" to specify that it's the AutoHotkey help file that's opened, not a CodeQuickTester help file? Keep up the good work. Cheers.
I'm glad you like it! Configuring the window size and having it remember the last window size is on the roadmap already, so you can expect to see it in the future.

The help file link could be a little more descriptive, though I don't think it's especially misleading as it is. I can definitely update it to say something like "Open AHK Help File" instead if you'd like.
CyL0N wrote: Bug: Line Numbering Gets Messed Up With Ctrl+Home, Ctrl+End., given a script with more than 200lines...
Yeah that's a known issue. I have no idea why it does that but if you figure it out let me know :lol:. If not, it's on my list of things to look at in the future.
CyL0N
Posts: 211
Joined: 27 Sep 2018, 09:58

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

24 Nov 2018, 21:15

GeekDude wrote:
24 Nov 2018, 14:23
By ribbon I really only meant, https://i.imgur.com/VvDa2jx.png ,looking at it closer it looks so much like a ribbon GUI,though not so much now I know its not.

Can I possibly get an example GUI to get that effect?

I didn't know you could do that with margins... though I recall removing button styles removed button borders...hmm...
live ? long & prosper : regards
CyL0N
Posts: 211
Joined: 27 Sep 2018, 09:58

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

25 Nov 2018, 22:07

GeekDude wrote:
24 Nov 2018, 14:23
CyL0N wrote: Bug: Line Numbering Gets Messed Up With Ctrl+Home, Ctrl+End., given a script with more than 200lines...
Yeah that's a known issue. I have no idea why it does that but if you figure it out let me know :lol:. If not, it's on my list of things to look at in the future.
It seems to be an issue with desynchronised controls,here's my fix:

Code: Select all

#IfWinActive CodeQuickTester
^End::
ControlGet, lnID, Hwnd,, RICHEDIT50W2, A	;ln = lineNumbering control ID
ControlGet, ecID, Hwnd,, RICHEDIT50W1, A	;main edit control ID
lastLine := GetLineCount(lnID), ScrollLines(lastLine,lnID), ScrollLines(lastLine,ecID)
ControlClick, RICHEDIT50W1
ControlSend, RICHEDIT50W1, {PgUp}{PgDn}, A
Return
;======================================================================================
#IfWinActive
ScrollLines(lines,hWnd="") {
	static EM_LINESCROLL := 0xB6
	PostMessage, EM_LINESCROLL, 0, lines-1, , ahk_id %hWnd%   ; 'lines-1' makes the line you wish to jump to visible
}
GetLineCount(ctrlHwnd){
	static EM_GETLINECOUNT = 0xBA
	SendMessage, EM_GETLINECOUNT,,,, ahk_id %ctrlHwnd%
	Return ErrorLevel
}
Last edited by CyL0N on 26 Nov 2018, 22:30, edited 2 times in total.
live ? long & prosper : regards
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

26 Nov 2018, 19:28

I bet there is a way to directly he the number of lines in the control instead of looping over them
CyL0N
Posts: 211
Joined: 27 Sep 2018, 09:58

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

26 Nov 2018, 20:23

kczx3 wrote:
26 Nov 2018, 19:28
I bet there is a way to directly he the number of lines in the control instead of looping over them
Perhaps,but even internally looping is how the line number is retrieved. Search for this.Settings.Gutter.Width.
live ? long & prosper : regards
CyL0N
Posts: 211
Joined: 27 Sep 2018, 09:58

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

26 Nov 2018, 20:50

kczx3 wrote:
26 Nov 2018, 20:36
https://docs.microsoft.com/en-us/window ... tlinecount

Though maybe word wrap messes it up for this tool?
Works well,updated my post accordingly.

Cheers.
live ? long & prosper : regards
0x00
Posts: 89
Joined: 22 Jan 2019, 13:12

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

01 Feb 2019, 08:02

Bug Report: Cancelling Rename discards filename.




Love this little thing,been so long since I actually used anything else. Cheers Dude.
hotkeyguy
Posts: 170
Joined: 11 Oct 2014, 12:22

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

31 May 2019, 07:57

Hello again,

great would be
  1. some global variables, with prefix e. g. cqt_x/y/w/h, for positioning own GUIs in relation to the CQT GUI.
    Especially when AlwaysOnTop, own GUIs are hidden,
  2. Run [F5] should toggle between the two modes Run and Kill.
    Don't know whether that is possible but when the own GUI is closed mode should be Run again,
  3. a special folder for templates and an corresponing Open Template... menu item,
  4. a MRU (most recently used) file list,
  5. all menu items should have mnemonics (the [Alt+_] combinations) or much better shortcuts.

Many thanks for all your efforts and greetings
hotkeyguy
User avatar
Hellbent
Posts: 2102
Joined: 23 Sep 2017, 13:34

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

10 Dec 2019, 11:22

Apologies if this has been asked / answered before.

How can I go about changing the default text in the script

e.g.

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
I want to change it to something like this:

Code: Select all

#SingleInstance, Force
#NoEnv
SetBatchLines, -1



return
GuiClose:
GuiContextMenu:
*ESC::
	ExitApp

***Edit***
Where are my manners....
Thanks for sharing this script GeekDude :thumbup:
Last edited by Hellbent on 12 Dec 2019, 09:34, edited 1 time in total.
gregster
Posts: 8921
Joined: 30 Sep 2013, 06:48

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

10 Dec 2019, 12:05

@Hellbent, have a look into Settings.ini. There, quite at the top, you can define a path to the template file you want to use.

Code: Select all

; Path to a file containing the starting contents of the editor. By default
; this is set to the path used by Explorer when you right click in a folder
; and select "New > AutoHotkey Script". This path can be relative to the
; working directory, which is useful when running in a portable environment.
DefaultPath = C:\Windows\ShellNew\Template.ahk
I personally use no template at all in CQT (I paste one via hotkey, if necessary - via the clipboard, because recent versions of CQT - with autocomplete - don't respond well to sending) and just commented that file path out, but you could change it to another template file, I guess.
User avatar
Hellbent
Posts: 2102
Joined: 23 Sep 2017, 13:34

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

10 Dec 2019, 12:48

@gregster

I was actually thinking of adding something like what you have as a hotkey to my quick run script when I was posting.
I might still do that (Scite has always just created blank new scripts and I'm used to that but the option for a simple template like this is nice.), but in regards to this script, because I'll only be using it for stuff that I will toss out as soon as I'm done with it I would like to have that template in place for every instance. I often find it very annoying when trying someones code and they don't have #singleInstance or a exit hotkey etc. This script is just so I can stop having a million "temp forum help" or "temp yt help" etc. scripts littered throughout my folders, doubtfully anything else.

Thank you for the assist, I was able to get it all sorted. :salute:
User avatar
boiler
Posts: 16768
Joined: 21 Dec 2014, 02:44

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

10 Dec 2019, 13:14

Hellbent wrote:
10 Dec 2019, 12:48
...or a exit hotkey etc.
That's one thing I like about CQT. It has a built-in exit hotkey.
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

27 May 2020, 11:08

Made a few changes to continuation sections, to allow users to specify the language/highlighter to be used inside. Supported languages include AHK, CSS, JS, and HTML. If an appropriate language-specifying comment is omitted, it defaults to "string" formatting, which is that desaturated reddish color by default.

No updates have been pushed yet, though.

Image
Nixcalo
Posts: 116
Joined: 06 Feb 2018, 04:24

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

26 Jul 2020, 07:53

HI GeekDude!

I saw that your script now supported Unicode charactes. However, I have the EXE version and when I copy a script from Notepad++ ans paste it into CodeQuickTester, all Unicode characters are lost. Do you know why and whether there is a workaround/fix for it?
Believe it or not, I use the character "╚" in my script (in order to split arrays)...
Nixcalo
Posts: 116
Joined: 06 Feb 2018, 04:24

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

18 Mar 2021, 21:18

I use CodeQuickTester and I love it, but I was wondering how could I modify the template so, when I write a new script from scratch, it starts with some default lines (for instance, the #include lines I use for my scripts). I always use the same libraries but I don't know how I can set CQT to start with a particular script template. Have searched, and never found anything.
gregster
Posts: 8921
Joined: 30 Sep 2013, 06:48

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

18 Mar 2021, 22:26

Nixcalo wrote:
18 Mar 2021, 21:18
I use CodeQuickTester and I love it, but I was wondering how could I modify the template so, when I write a new script from scratch, it starts with some default lines (for instance, the #include lines I use for my scripts). I always use the same libraries but I don't know how I can set CQT to start with a particular script template. Have searched, and never found anything.
Afaik, you can use settings.ini:
gregster wrote:
10 Dec 2019, 12:05
have a look into Settings.ini. There, quite at the top, you can define a path to the template file you want to use.

Code: Select all

; Path to a file containing the starting contents of the editor. By default
; this is set to the path used by Explorer when you right click in a folder
; and select "New > AutoHotkey Script". This path can be relative to the
; working directory, which is useful when running in a portable environment.
DefaultPath = C:\Windows\ShellNew\Template.ahk
User avatar
emrekarahan0001
Posts: 25
Joined: 18 Jan 2021, 14:19

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

20 Jan 2022, 04:57

A little bit color change.. 8-)

Code: Select all

; CodeQuickTester v2.8
; Copyright GeekDude 2018
; https://github.com/G33kDude/CodeQuickTester

#SingleInstance, Off
#NoEnv
SetBatchLines, -1
SetWorkingDir, %A_ScriptDir%

global B_Params := []
Loop, %0%
	B_Params.Push(%A_Index%)

Menu, Tray, Icon, %A_AhkPath%, 2
FileEncoding, UTF-8

Settings :=
( LTrim Join Comments
{
	; File path for the starting contents
	"DefaultPath": "C:\Windows\ShellNew\Template.ahk",

	; When True, this setting may conflict with other instances of CQT
	"GlobalRun": False,

	; Script options
	"AhkPath": A_AhkPath,
	"Params": "",

	; Editor (colors are 0xBBGGRR)
	"FGColor": 0xEDEDCD,
	"BGColor": 0x1D1D1D,
	"TabSize": 4,
	"Font": {
		"Typeface": "Consolas",
		"Size": 11,
		"Bold": False
	},
	"Gutter": {
		; Width in pixels. Make this larger when using
		; larger fonts. Set to 0 to disable the gutter.
		"Width": 40,

		"FGColor": 0x9FAFAF,
		"BGColor": 0x262626
	},

	; Highlighter (colors are 0xRRGGBB)
	"UseHighlighter": True,
	"Highlighter": "HighlightAHK",
	"HighlightDelay": 200, ; Delay until the user is finished typing
	"Colors": {
		"Comments":     0x7F9F7F,
		"Functions":    0x7CC8CF,
		"Keywords":     0xE4EDED,
		"Multiline":    0x7F9F7F,
		"Numbers":      0xF79B57,
		"Punctuation":  0x97C0EB,
		"Strings":      0xE8C159,
		"A_Builtins":   0x0096FF,
		"Commands":     0xF82744,
		"Directives":   0x43EEA4,
		"Flow":         0xF2F0E9,
		"KeyNames":     0x6ECCF3
	},

	; Auto-Indenter
	"Indent": "`t",

	; Pastebin
	"DefaultName": A_UserName,
	"DefaultDesc": "Pasted with CodeQuickTester",

	; AutoComplete
	"UseAutoComplete": True,
	"ACListRebuildDelay": 500 ; Delay until the user is finished typing
}
)

; Overlay any external settings onto the above defaults
if FileExist("Settings.ini")
{
	ExtSettings := Ini_Load(FileOpen("Settings.ini", "r").Read())
	for k, v in ExtSettings
		if IsObject(v)
			v.base := Settings[k]
	ExtSettings.base := Settings
	Settings := ExtSettings
}

Tester := new CodeQuickTester(Settings)
Tester.RegisterCloseCallback(Func("TesterClose"))
return

#If Tester.Exec.Status == 0 ; Running

~*Escape::Tester.Exec.Terminate()

#If (Tester.Settings.GlobalRun && Tester.Exec.Status == 0) ; Running

F5::
!r::
; Reloads
Tester.RunButton()
Tester.RunButton()
return

#If Tester.Settings.GlobalRun

F5::
!r::
Tester.RunButton()
return

#If

TesterClose(Tester)
{
	ExitApp
}

/*
	class RichCode({"TabSize": 4     ; Width of a tab in characters
	, "Indent": "`t"             ; What text to insert on indent
	, "FGColor": 0xRRGGBB        ; Foreground (text) color
	, "BGColor": 0xRRGGBB        ; Background color
	, "Font"                     ; Font to use
	: {"Typeface": "Courier New" ; Name of the typeface
	, "Size": 12             ; Font size in points
	, "Bold": False}         ; Bolded (True/False)
	
	
	; Whether to use the highlighter, or leave it as plain text
	, "UseHighlighter": True
	
	; Delay after typing before the highlighter is run
	, "HighlightDelay": 200
	
	; The highlighter function (FuncObj or name)
	; to generate the highlighted RTF. It will be passed
	; two parameters, the first being this settings array
	; and the second being the code to be highlighted
	, "Highlighter": Func("HighlightAHK")
	
	; The colors to be used by the highlighter function.
	; This is currently used only by the highlighter, not at all by the
	; RichCode class. As such, the RGB ordering is by convention only.
	; You can add as many colors to this array as you want.
	, "Colors"
	: [0xRRGGBB
	, 0xRRGGBB
	, 0xRRGGBB,
	, 0xRRGGBB]})
*/

class RichCode
{
	static Msftedit := DllCall("LoadLibrary", "Str", "Msftedit.dll")
	static IID_ITextDocument := "{8CC497C0-A1DF-11CE-8098-00AA0047BE5D}"
	static MenuItems := ["Cut", "Copy", "Paste", "Delete", "", "Select All", ""
	, "UPPERCASE", "lowercase", "TitleCase"]
	
	_Frozen := False
	
	; --- Static Methods ---
	
	BGRFromRGB(RGB)
	{
		return RGB>>16&0xFF | RGB&0xFF00 | RGB<<16&0xFF0000
	}
	
	; --- Properties ---
	
	Value[]
	{
		get {
			GuiControlGet, Code,, % this.hWnd
			return Code
		}
		
		set {
			this.Highlight(Value)
			return Value
		}
	}
	
	; TODO: reserve and reuse memory
	Selection[i:=0]
	{
		get {
			VarSetCapacity(CHARRANGE, 8, 0)
			this.SendMsg(0x434, 0, &CHARRANGE) ; EM_EXGETSEL
			Out := [NumGet(CHARRANGE, 0, "Int"), NumGet(CHARRANGE, 4, "Int")]
			return i ? Out[i] : Out
		}
		
		set {
			if i
				Temp := this.Selection, Temp[i] := Value, Value := Temp
			VarSetCapacity(CHARRANGE, 8, 0)
			NumPut(Value[1], &CHARRANGE, 0, "Int") ; cpMin
			NumPut(Value[2], &CHARRANGE, 4, "Int") ; cpMax
			this.SendMsg(0x437, 0, &CHARRANGE) ; EM_EXSETSEL
			return Value
		}
	}
	
	SelectedText[]
	{
		get {
			Selection := this.Selection, Length := Selection[2] - Selection[1]
			VarSetCapacity(Buffer, (Length + 1) * 2) ; +1 for null terminator
			if (this.SendMsg(0x43E, 0, &Buffer) > Length) ; EM_GETSELTEXT
				throw Exception("Text larger than selection! Buffer overflow!")
			Text := StrGet(&Buffer, Selection[2]-Selection[1], "UTF-16")
			return StrReplace(Text, "`r", "`n")
		}
		
		set {
			this.SendMsg(0xC2, 1, &Value) ; EM_REPLACESEL
			this.Selection[1] -= StrLen(Value)
			return Value
		}
	}
	
	EventMask[]
	{
		get {
			return this._EventMask
		}
		
		set {
			this._EventMask := Value
			this.SendMsg(0x445, 0, Value) ; EM_SETEVENTMASK
			return Value
		}
	}
	
	UndoSuspended[]
	{
		get {
			return this._UndoSuspended
		}
		
		set {
			try ; ITextDocument is not implemented in WINE
			{
				if Value
					this.ITextDocument.Undo(-9999995) ; tomSuspend
				else
					this.ITextDocument.Undo(-9999994) ; tomResume
			}
			return this._UndoSuspended := !!Value
		}
	}
	
	Frozen[]
	{
		get {
			return this._Frozen
		}
		
		set {
			if (Value && !this._Frozen)
			{
				try ; ITextDocument is not implemented in WINE
					this.ITextDocument.Freeze()
				catch
					GuiControl, -Redraw, % this.hWnd
			}
			else if (!Value && this._Frozen)
			{
				try ; ITextDocument is not implemented in WINE
					this.ITextDocument.Unfreeze()
				catch
					GuiControl, +Redraw, % this.hWnd
			}
			return this._Frozen := !!Value
		}
	}
	
	Modified[]
	{
		get {
			return this.SendMsg(0xB8, 0, 0) ; EM_GETMODIFY
		}
		
		set {
			this.SendMsg(0xB9, Value, 0) ; EM_SETMODIFY
			return Value
		}
	}
	
	; --- Construction, Destruction, Meta-Functions ---
	
	__New(Settings, Options:="")
	{
		static Test
		this.Settings := Settings
		FGColor := this.BGRFromRGB(Settings.FGColor)
		BGColor := this.BGRFromRGB(Settings.BGColor)
		
		Gui, Add, Custom, ClassRichEdit50W hWndhWnd +0x5031b1c4 +E0x20000 %Options%
		this.hWnd := hWnd
		
		; Enable WordWrap in RichEdit control ("WordWrap" : true)
		if this.Settings.WordWrap
			SendMessage, 0x0448, 0, 0, , % "ahk_id " . This.HWND
		
		; Register for WM_COMMAND and WM_NOTIFY events
		; NOTE: this prevents garbage collection of
		; the class until the control is destroyed
		this.EventMask := 1 ; ENM_CHANGE
		CtrlEvent := this.CtrlEvent.Bind(this)
		GuiControl, +g, %hWnd%, %CtrlEvent%
		
		; Set background color
		this.SendMsg(0x443, 0, BGColor) ; EM_SETBKGNDCOLOR
		
		; Set character format
		VarSetCapacity(CHARFORMAT2, 116, 0)
		NumPut(116,                    CHARFORMAT2, 0,  "UInt")       ; cbSize      = sizeof(CHARFORMAT2)
		NumPut(0xE0000000,             CHARFORMAT2, 4,  "UInt")       ; dwMask      = CFM_COLOR|CFM_FACE|CFM_SIZE
		NumPut(FGColor,                CHARFORMAT2, 20, "UInt")       ; crTextColor = 0xBBGGRR
		NumPut(Settings.Font.Size*20,  CHARFORMAT2, 12, "UInt")       ; yHeight     = twips
		StrPut(Settings.Font.Typeface, &CHARFORMAT2+26, 32, "UTF-16") ; szFaceName  = TCHAR
		this.SendMsg(0x444, 0, &CHARFORMAT2) ; EM_SETCHARFORMAT
		
		; Set tab size to 4 for non-highlighted code
		VarSetCapacity(TabStops, 4, 0), NumPut(Settings.TabSize*4, TabStops, "UInt")
		this.SendMsg(0x0CB, 1, &TabStops) ; EM_SETTABSTOPS
		
		; Change text limit from 32,767 to max
		this.SendMsg(0x435, 0, -1) ; EM_EXLIMITTEXT
		
		; Bind for keyboard events
		; Use a pointer to prevent reference loop
		this.OnMessageBound := this.OnMessage.Bind(&this)
		OnMessage(0x100, this.OnMessageBound) ; WM_KEYDOWN
		OnMessage(0x205, this.OnMessageBound) ; WM_RBUTTONUP
		
		; Bind the highlighter
		this.HighlightBound := this.Highlight.Bind(&this)
		
		; Create the right click menu
		this.MenuName := this.__Class . &this
		RCMBound := this.RightClickMenu.Bind(&this)
		for Index, Entry in this.MenuItems
			Menu, % this.MenuName, Add, %Entry%, %RCMBound%
		
		; Get the ITextDocument object
		VarSetCapacity(pIRichEditOle, A_PtrSize, 0)
		this.SendMsg(0x43C, 0, &pIRichEditOle) ; EM_GETOLEINTERFACE
		this.pIRichEditOle := NumGet(pIRichEditOle, 0, "UPtr")
		this.IRichEditOle := ComObject(9, this.pIRichEditOle, 1), ObjAddRef(this.pIRichEditOle)
		this.pITextDocument := ComObjQuery(this.IRichEditOle, this.IID_ITextDocument)
		this.ITextDocument := ComObject(9, this.pITextDocument, 1), ObjAddRef(this.pITextDocument)
	}
	
	RightClickMenu(ItemName, ItemPos, MenuName)
	{
		if !IsObject(this)
			this := Object(this)
		
		if (ItemName == "Cut")
			Clipboard := this.SelectedText, this.SelectedText := ""
		else if (ItemName == "Copy")
			Clipboard := this.SelectedText
		else if (ItemName == "Paste")
			this.SelectedText := Clipboard
		else if (ItemName == "Delete")
			this.SelectedText := ""
		else if (ItemName == "Select All")
			this.Selection := [0, -1]
		else if (ItemName == "UPPERCASE")
			this.SelectedText := Format("{:U}", this.SelectedText)
		else if (ItemName == "lowercase")
			this.SelectedText := Format("{:L}", this.SelectedText)
		else if (ItemName == "TitleCase")
			this.SelectedText := Format("{:T}", this.SelectedText)
	}
	
	__Delete()
	{
		; Release the ITextDocument object
		this.ITextDocument := "", ObjRelease(this.pITextDocument)
		this.IRichEditOle := "", ObjRelease(this.pIRichEditOle)
		
		; Release the OnMessage handlers
		OnMessage(0x100, this.OnMessageBound, 0) ; WM_KEYDOWN
		OnMessage(0x205, this.OnMessageBound, 0) ; WM_RBUTTONUP
		
		; Destroy the right click menu
		Menu, % this.MenuName, Delete
		
		HighlightBound := this.HighlightBound
		SetTimer, %HighlightBound%, Delete
	}
	
	; --- Event Handlers ---
	
	OnMessage(wParam, lParam, Msg, hWnd)
	{
		if !IsObject(this)
			this := Object(this)
		if (hWnd != this.hWnd)
			return
		
		if (Msg == 0x100) ; WM_KEYDOWN
		{
			if (wParam == GetKeyVK("Tab"))
			{
				; Indentation
				Selection := this.Selection
				if GetKeyState("Shift")
					this.IndentSelection(True) ; Reverse
				else if (Selection[2] - Selection[1]) ; Something is selected
					this.IndentSelection()
				else
				{
					; TODO: Trim to size needed to reach next TabSize
					this.SelectedText := this.Settings.Indent
					this.Selection[1] := this.Selection[2] ; Place cursor after
				}
				return False
			}
			else if (wParam == GetKeyVK("Escape")) ; Normally closes the window
				return False
			else if (wParam == GetKeyVK("v") && GetKeyState("Ctrl"))
			{
				this.SelectedText := Clipboard ; Strips formatting
				this.Selection[1] := this.Selection[2] ; Place cursor after
				return False
			}
		}
		else if (Msg == 0x205) ; WM_RBUTTONUP
		{
			Menu, % this.MenuName, Show
			return False
		}
	}
	
	CtrlEvent(CtrlHwnd, GuiEvent, EventInfo, _ErrorLevel:="")
	{
		if (GuiEvent == "Normal" && EventInfo == 0x300) ; EN_CHANGE
		{
			; Delay until the user is finished changing the document
			HighlightBound := this.HighlightBound
			SetTimer, %HighlightBound%, % -Abs(this.Settings.HighlightDelay)
		}
	}
	
	; --- Methods ---
	
	; First parameter is taken as a replacement value
	; Variadic form is used to detect when a parameter is given,
	; regardless of content
	Highlight(NewVal*)
	{
		if !IsObject(this)
			this := Object(this)
		if !(this.Settings.UseHighlighter && this.Settings.Highlighter)
		{
			if NewVal.Length()
				GuiControl,, % this.hWnd, % NewVal[1]
			return
		}
		
		; Freeze the control while it is being modified, stop change event
		; generation, suspend the undo buffer, buffer any input events
		PrevFrozen := this.Frozen, this.Frozen := True
		PrevEventMask := this.EventMask, this.EventMask := 0 ; ENM_NONE
		PrevUndoSuspended := this.UndoSuspended, this.UndoSuspended := True
		PrevCritical := A_IsCritical
		Critical, 1000
		
		; Run the highlighter
		Highlighter := this.Settings.Highlighter
		RTF := %Highlighter%(this.Settings, NewVal.Length() ? NewVal[1] : this.Value)
		
		; "TRichEdit suspend/resume undo function"
		; https://stackoverflow.com/a/21206620
		
		; Save the rich text to a UTF-8 buffer
		VarSetCapacity(Buf, StrPut(RTF, "UTF-8"), 0)
		StrPut(RTF, &Buf, "UTF-8")
		
		; Set up the necessary structs
		VarSetCapacity(ZOOM,      8, 0) ; Zoom Level
		VarSetCapacity(POINT,     8, 0) ; Scroll Pos
		VarSetCapacity(CHARRANGE, 8, 0) ; Selection
		VarSetCapacity(SETTEXTEX, 8, 0) ; SetText Settings
		NumPut(1, SETTEXTEX, 0, "UInt") ; flags = ST_KEEPUNDO
		
		; Save the scroll and cursor positions, update the text,
		; then restore the scroll and cursor positions
		MODIFY := this.SendMsg(0xB8, 0, 0)    ; EM_GETMODIFY
		this.SendMsg(0x4E0, &ZOOM, &ZOOM+4)   ; EM_GETZOOM
		this.SendMsg(0x4DD, 0, &POINT)        ; EM_GETSCROLLPOS
		this.SendMsg(0x434, 0, &CHARRANGE)    ; EM_EXGETSEL
		this.SendMsg(0x461, &SETTEXTEX, &Buf) ; EM_SETTEXTEX
		this.SendMsg(0x437, 0, &CHARRANGE)    ; EM_EXSETSEL
		this.SendMsg(0x4DE, 0, &POINT)        ; EM_SETSCROLLPOS
		this.SendMsg(0x4E1, NumGet(ZOOM, "UInt")
		, NumGet(ZOOM, 4, "UInt"))        ; EM_SETZOOM
		this.SendMsg(0xB9, MODIFY, 0)         ; EM_SETMODIFY
		
		; Restore previous settings
		Critical, %PrevCritical%
		this.UndoSuspended := PrevUndoSuspended
		this.EventMask := PrevEventMask
		this.Frozen := PrevFrozen
	}
	
	IndentSelection(Reverse:=False, Indent:="")
	{
		; Freeze the control while it is being modified, stop change event
		; generation, buffer any input events
		PrevFrozen := this.Frozen, this.Frozen := True
		PrevEventMask := this.EventMask, this.EventMask := 0 ; ENM_NONE
		PrevCritical := A_IsCritical
		Critical, 1000
		
		if (Indent == "")
			Indent := this.Settings.Indent
		IndentLen := StrLen(Indent)
		
		; Select back to the start of the first line
		Min := this.Selection[1]
		Top := this.SendMsg(0x436, 0, Min) ; EM_EXLINEFROMCHAR
		TopLineIndex := this.SendMsg(0xBB, Top, 0) ; EM_LINEINDEX
		this.Selection[1] := TopLineIndex
		
		; TODO: Insert newlines using SetSel/ReplaceSel to avoid having to call
		; the highlighter again
		Text := this.SelectedText
		if Reverse
		{
			; Remove indentation appropriately
			Loop, Parse, Text, `n, `r
			{
				if (InStr(A_LoopField, Indent) == 1)
				{
					Out .= "`n" SubStr(A_LoopField, 1+IndentLen)
					if (A_Index == 1)
						Min -= IndentLen
				}
				else
					Out .= "`n" A_LoopField
			}
			this.SelectedText := SubStr(Out, 2)
			
			; Move the selection start back, but never onto the previous line
			this.Selection[1] := Min < TopLineIndex ? TopLineIndex : Min
		}
		else
		{
			; Add indentation appropriately
			Trailing := (SubStr(Text, 0) == "`n")
			Temp := Trailing ? SubStr(Text, 1, -1) : Text
			Loop, Parse, Temp, `n, `r
				Out .= "`n" Indent . A_LoopField
			this.SelectedText := SubStr(Out, 2) . (Trailing ? "`n" : "")
			
			; Move the selection start forward
			this.Selection[1] := Min + IndentLen
		}
		
		this.Highlight()
		
		; Restore previous settings
		Critical, %PrevCritical%
		this.EventMask := PrevEventMask
		
		; When content changes cause the horizontal scrollbar to disappear,
		; unfreezing causes the scrollbar to jump. To solve this, jump back
		; after unfreezing. This will cause a flicker when that edge case
		; occurs, but it's better than the alternative.
		VarSetCapacity(POINT, 8, 0)
		this.SendMsg(0x4DD, 0, &POINT) ; EM_GETSCROLLPOS
		this.Frozen := PrevFrozen
		this.SendMsg(0x4DE, 0, &POINT) ; EM_SETSCROLLPOS
	}
	
	; --- Helper/Convenience Methods ---
	
	SendMsg(Msg, wParam, lParam)
	{
		SendMessage, Msg, wParam, lParam,, % "ahk_id" this.hWnd
		return ErrorLevel
	}
}
GenHighlighterCache(Settings)
{
	if Settings.HasKey("Cache")
		return
	Cache := Settings.Cache := {}
	
	
	; --- Process Colors ---
	Cache.Colors := Settings.Colors.Clone()
	
	; Inherit from the Settings array's base
	BaseSettings := Settings
	while (BaseSettings := BaseSettings.Base)
		for Name, Color in BaseSettings.Colors
			if !Cache.Colors.HasKey(Name)
				Cache.Colors[Name] := Color
	
	; Include the color of plain text
	if !Cache.Colors.HasKey("Plain")
		Cache.Colors.Plain := Settings.FGColor
	
	; Create a Name->Index map of the colors
	Cache.ColorMap := {}
	for Name, Color in Cache.Colors
		Cache.ColorMap[Name] := A_Index
	
	
	; --- Generate the RTF headers ---
	RTF := "{\urtf"
	
	; Color Table
	RTF .= "{\colortbl;"
	for Name, Color in Cache.Colors
	{
		RTF .= "\red"   Color>>16 & 0xFF
		RTF .= "\green" Color>>8  & 0xFF
		RTF .= "\blue"  Color     & 0xFF ";"
	}
	RTF .= "}"
	
	; Font Table
	if Settings.Font
	{
		FontTable .= "{\fonttbl{\f0\fmodern\fcharset0 "
		FontTable .= Settings.Font.Typeface
		FontTable .= ";}}"
		RTF .= "\fs" Settings.Font.Size * 2 ; Font size (half-points)
		if Settings.Font.Bold
			RTF .= "\b"
	}
	
	; Tab size (twips)
	RTF .= "\deftab" GetCharWidthTwips(Settings.Font) * Settings.TabSize
	
	Cache.RTFHeader := RTF
}

GetCharWidthTwips(Font)
{
	static Cache := {}
	
	if Cache.HasKey(Font.Typeface "_" Font.Size "_" Font.Bold)
		return Cache[Font.Typeface "_" font.Size "_" Font.Bold]
	
	; Calculate parameters of CreateFont
	Height := -Round(Font.Size*A_ScreenDPI/72)
	Weight := 400+300*(!!Font.Bold)
	Face := Font.Typeface
	
	; Get the width of "x"
	hDC := DllCall("GetDC", "UPtr", 0)
	hFont := DllCall("CreateFont"
	, "Int", Height ; _In_ int     nHeight,
	, "Int", 0      ; _In_ int     nWidth,
	, "Int", 0      ; _In_ int     nEscapement,
	, "Int", 0      ; _In_ int     nOrientation,
	, "Int", Weight ; _In_ int     fnWeight,
	, "UInt", 0     ; _In_ DWORD   fdwItalic,
	, "UInt", 0     ; _In_ DWORD   fdwUnderline,
	, "UInt", 0     ; _In_ DWORD   fdwStrikeOut,
	, "UInt", 0     ; _In_ DWORD   fdwCharSet, (ANSI_CHARSET)
	, "UInt", 0     ; _In_ DWORD   fdwOutputPrecision, (OUT_DEFAULT_PRECIS)
	, "UInt", 0     ; _In_ DWORD   fdwClipPrecision, (CLIP_DEFAULT_PRECIS)
	, "UInt", 0     ; _In_ DWORD   fdwQuality, (DEFAULT_QUALITY)
	, "UInt", 0     ; _In_ DWORD   fdwPitchAndFamily, (FF_DONTCARE|DEFAULT_PITCH)
	, "Str", Face   ; _In_ LPCTSTR lpszFace
	, "UPtr")
	hObj := DllCall("SelectObject", "UPtr", hDC, "UPtr", hFont, "UPtr")
	VarSetCapacity(SIZE, 8, 0)
	DllCall("GetTextExtentPoint32", "UPtr", hDC, "Str", "x", "Int", 1, "UPtr", &SIZE)
	DllCall("SelectObject", "UPtr", hDC, "UPtr", hObj, "UPtr")
	DllCall("DeleteObject", "UPtr", hFont)
	DllCall("ReleaseDC", "UPtr", 0, "UPtr", hDC)
	
	; Convert to twpis
	Twips := Round(NumGet(SIZE, 0, "UInt")*1440/A_ScreenDPI)
	Cache[Font.Typeface "_" Font.Size "_" Font.Bold] := Twips
	return Twips
}

EscapeRTF(Code)
{
	for each, Char in ["\", "{", "}", "`n"]
		Code := StrReplace(Code, Char, "\" Char)
	return StrReplace(StrReplace(Code, "`t", "\tab "), "`r")
}

HighlightAHK(Settings, ByRef Code)
{
	static Flow := "break|byref|catch|class|continue|else|exit|exitapp|finally|for|global|gosub|goto|if|ifequal|ifexist|ifgreater|ifgreaterorequal|ifinstring|ifless|iflessorequal|ifmsgbox|ifnotequal|ifnotexist|ifnotinstring|ifwinactive|ifwinexist|ifwinnotactive|ifwinnotexist|local|loop|onexit|pause|return|settimer|sleep|static|suspend|throw|try|until|var|while"
	, Commands := "autotrim|blockinput|clipwait|control|controlclick|controlfocus|controlget|controlgetfocus|controlgetpos|controlgettext|controlmove|controlsend|controlsendraw|controlsettext|coordmode|critical|detecthiddentext|detecthiddenwindows|drive|driveget|drivespacefree|edit|envadd|envdiv|envget|envmult|envset|envsub|envupdate|fileappend|filecopy|filecopydir|filecreatedir|filecreateshortcut|filedelete|fileencoding|filegetattrib|filegetshortcut|filegetsize|filegettime|filegetversion|fileinstall|filemove|filemovedir|fileread|filereadline|filerecycle|filerecycleempty|fileremovedir|fileselectfile|fileselectfolder|filesetattrib|filesettime|formattime|getkeystate|groupactivate|groupadd|groupclose|groupdeactivate|gui|guicontrol|guicontrolget|hotkey|imagesearch|inidelete|iniread|iniwrite|input|inputbox|keyhistory|keywait|listhotkeys|listlines|listvars|menu|mouseclick|mouseclickdrag|mousegetpos|mousemove|msgbox|outputdebug|pixelgetcolor|pixelsearch|postmessage|process|progress|random|regdelete|regread|regwrite|reload|run|runas|runwait|send|sendevent|sendinput|sendlevel|sendmessage|sendmode|sendplay|sendraw|setbatchlines|setcapslockstate|setcontroldelay|setdefaultmousespeed|setenv|setformat|setkeydelay|setmousedelay|setnumlockstate|setregview|setscrolllockstate|setstorecapslockmode|settitlematchmode|setwindelay|setworkingdir|shutdown|sort|soundbeep|soundget|soundgetwavevolume|soundplay|soundset|soundsetwavevolume|splashimage|splashtextoff|splashtexton|splitpath|statusbargettext|statusbarwait|stringcasesense|stringgetpos|stringleft|stringlen|stringlower|stringmid|stringreplace|stringright|stringsplit|stringtrimleft|stringtrimright|stringupper|sysget|thread|tooltip|transform|traytip|urldownloadtofile|winactivate|winactivatebottom|winclose|winget|wingetactivestats|wingetactivetitle|wingetclass|wingetpos|wingettext|wingettitle|winhide|winkill|winmaximize|winmenuselectitem|winminimize|winminimizeall|winminimizeallundo|winmove|winrestore|winset|winsettitle|winshow|winwait|winwaitactive|winwaitclose|winwaitnotactive"
	, Functions := "abs|acos|array|asc|asin|atan|ceil|chr|comobjactive|comobjarray|comobjconnect|comobjcreate|comobject|comobjenwrap|comobjerror|comobjflags|comobjget|comobjmissing|comobjparameter|comobjquery|comobjtype|comobjunwrap|comobjvalue|cos|dllcall|exception|exp|fileexist|fileopen|floor|func|getkeyname|getkeysc|getkeystate|getkeyvk|il_add|il_create|il_destroy|instr|isbyref|isfunc|islabel|isobject|isoptional|ln|log|ltrim|lv_add|lv_delete|lv_deletecol|lv_getcount|lv_getnext|lv_gettext|lv_insert|lv_insertcol|lv_modify|lv_modifycol|lv_setimagelist|mod|numget|numput|objaddref|objclone|object|objgetaddress|objgetcapacity|objhaskey|objinsert|objinsertat|objlength|objmaxindex|objminindex|objnewenum|objpop|objpush|objrawset|objrelease|objremove|objremoveat|objsetcapacity|onmessage|ord|regexmatch|regexreplace|registercallback|round|rtrim|sb_seticon|sb_setparts|sb_settext|sin|sqrt|strget|strlen|strput|strsplit|substr|tan|trim|tv_add|tv_delete|tv_get|tv_getchild|tv_getcount|tv_getnext|tv_getparent|tv_getprev|tv_getselection|tv_gettext|tv_modify|tv_setimagelist|varsetcapacity|winactive|winexist|_addref|_clone|_getaddress|_getcapacity|_haskey|_insert|_maxindex|_minindex|_newenum|_release|_remove|_setcapacity"
	, Keynames := "alt|altdown|altup|appskey|backspace|blind|browser_back|browser_favorites|browser_forward|browser_home|browser_refresh|browser_search|browser_stop|bs|capslock|click|control|ctrl|ctrlbreak|ctrldown|ctrlup|del|delete|down|end|enter|esc|escape|f1|f10|f11|f12|f13|f14|f15|f16|f17|f18|f19|f2|f20|f21|f22|f23|f24|f3|f4|f5|f6|f7|f8|f9|home|ins|insert|joy1|joy10|joy11|joy12|joy13|joy14|joy15|joy16|joy17|joy18|joy19|joy2|joy20|joy21|joy22|joy23|joy24|joy25|joy26|joy27|joy28|joy29|joy3|joy30|joy31|joy32|joy4|joy5|joy6|joy7|joy8|joy9|joyaxes|joybuttons|joyinfo|joyname|joypov|joyr|joyu|joyv|joyx|joyy|joyz|lalt|launch_app1|launch_app2|launch_mail|launch_media|lbutton|lcontrol|lctrl|left|lshift|lwin|lwindown|lwinup|mbutton|media_next|media_play_pause|media_prev|media_stop|numlock|numpad0|numpad1|numpad2|numpad3|numpad4|numpad5|numpad6|numpad7|numpad8|numpad9|numpadadd|numpadclear|numpaddel|numpaddiv|numpaddot|numpaddown|numpadend|numpadenter|numpadhome|numpadins|numpadleft|numpadmult|numpadpgdn|numpadpgup|numpadright|numpadsub|numpadup|pause|pgdn|pgup|printscreen|ralt|raw|rbutton|rcontrol|rctrl|right|rshift|rwin|rwindown|rwinup|scrolllock|shift|shiftdown|shiftup|space|tab|up|volume_down|volume_mute|volume_up|wheeldown|wheelleft|wheelright|wheelup|xbutton1|xbutton2"
	, Builtins := "base|clipboard|clipboardall|comspec|errorlevel|false|programfiles|true"
	, Keywords := "abort|abovenormal|activex|add|ahk_class|ahk_exe|ahk_group|ahk_id|ahk_pid|all|alnum|alpha|altsubmit|alttab|alttabandmenu|alttabmenu|alttabmenudismiss|alwaysontop|and|autosize|background|backgroundtrans|base|belownormal|between|bitand|bitnot|bitor|bitshiftleft|bitshiftright|bitxor|bold|border|bottom|button|buttons|cancel|capacity|caption|center|check|check3|checkbox|checked|checkedgray|choose|choosestring|click|clone|close|color|combobox|contains|controllist|controllisthwnd|count|custom|date|datetime|days|ddl|default|delete|deleteall|delimiter|deref|destroy|digit|disable|disabled|dpiscale|dropdownlist|edit|eject|enable|enabled|error|exit|expand|exstyle|extends|filesystem|first|flash|float|floatfast|focus|font|force|fromcodepage|getaddress|getcapacity|grid|group|groupbox|guiclose|guicontextmenu|guidropfiles|guiescape|guisize|haskey|hdr|hidden|hide|high|hkcc|hkcr|hkcu|hkey_classes_root|hkey_current_config|hkey_current_user|hkey_local_machine|hkey_users|hklm|hku|hotkey|hours|hscroll|hwnd|icon|iconsmall|id|idlast|ignore|imagelist|in|insert|integer|integerfast|interrupt|is|italic|join|label|lastfound|lastfoundexist|left|limit|lines|link|list|listbox|listview|localsameasglobal|lock|logoff|low|lower|lowercase|ltrim|mainwindow|margin|maximize|maximizebox|maxindex|menu|minimize|minimizebox|minmax|minutes|monitorcount|monitorname|monitorprimary|monitorworkarea|monthcal|mouse|mousemove|mousemoveoff|move|multi|na|new|no|noactivate|nodefault|nohide|noicon|nomainwindow|norm|normal|nosort|nosorthdr|nostandard|not|notab|notimers|number|off|ok|on|or|owndialogs|owner|parse|password|pic|picture|pid|pixel|pos|pow|priority|processname|processpath|progress|radio|range|rawread|rawwrite|read|readchar|readdouble|readfloat|readint|readint64|readline|readnum|readonly|readshort|readuchar|readuint|readushort|realtime|redraw|regex|region|reg_binary|reg_dword|reg_dword_big_endian|reg_expand_sz|reg_full_resource_descriptor|reg_link|reg_multi_sz|reg_qword|reg_resource_list|reg_resource_requirements_list|reg_sz|relative|reload|remove|rename|report|resize|restore|retry|rgb|right|rtrim|screen|seconds|section|seek|send|sendandmouse|serial|setcapacity|setlabel|shiftalttab|show|shutdown|single|slider|sortdesc|standard|status|statusbar|statuscd|strike|style|submit|sysmenu|tab|tab2|tabstop|tell|text|theme|this|tile|time|tip|tocodepage|togglecheck|toggleenable|toolwindow|top|topmost|transcolor|transparent|tray|treeview|type|uncheck|underline|unicode|unlock|updown|upper|uppercase|useenv|useerrorlevel|useunsetglobal|useunsetlocal|vis|visfirst|visible|vscroll|waitclose|wantctrla|wantf2|wantreturn|wanttab|wrap|write|writechar|writedouble|writefloat|writeint|writeint64|writeline|writenum|writeshort|writeuchar|writeuint|writeushort|xdigit|xm|xp|xs|yes|ym|yp|ys|__call|__delete|__get|__handle|__new|__set"
	, Needle := "
	( LTrim Join Comments
		ODims)
		((?:^|\s);[^\n]+)          ; Comments
		|(^\s*\/\*.+?\n\s*\*\/)    ; Multiline comments
		|((?:^|\s)#[^ \t\r\n,]+)   ; Directives
		|([+*!~&\/\\<>^|=?:
			,().```%{}\[\]\-]+)    ; Punctuation
		|\b(0x[0-9a-fA-F]+|[0-9]+) ; Numbers
		|(""[^""\r\n]*"")          ; Strings
		|\b(A_\w*|" Builtins ")\b  ; A_Builtins
		|\b(" Flow ")\b            ; Flow
		|\b(" Commands ")\b        ; Commands
		|\b(" Functions ")\b       ; Functions (builtin)
		|\b(" Keynames ")\b        ; Keynames
		|\b(" Keywords ")\b        ; Other keywords
		|(([a-zA-Z_$]+)(?=\())     ; Functions
	)"
	
	GenHighlighterCache(Settings)
	Map := Settings.Cache.ColorMap
	
	Pos := 1
	while (FoundPos := RegExMatch(Code, Needle, Match, Pos))
	{
		RTF .= "\cf" Map.Plain " "
		RTF .= EscapeRTF(SubStr(Code, Pos, FoundPos-Pos))
		
		; Flat block of if statements for performance
		if (Match.Value(1) != "")
			RTF .= "\cf" Map.Comments
		else if (Match.Value(2) != "")
			RTF .= "\cf" Map.Multiline
		else if (Match.Value(3) != "")
			RTF .= "\cf" Map.Directives
		else if (Match.Value(4) != "")
			RTF .= "\cf" Map.Punctuation
		else if (Match.Value(5) != "")
			RTF .= "\cf" Map.Numbers
		else if (Match.Value(6) != "")
			RTF .= "\cf" Map.Strings
		else if (Match.Value(7) != "")
			RTF .= "\cf" Map.A_Builtins
		else if (Match.Value(8) != "")
			RTF .= "\cf" Map.Flow
		else if (Match.Value(9) != "")
			RTF .= "\cf" Map.Commands
		else if (Match.Value(10) != "")
			RTF .= "\cf" Map.Functions
		else if (Match.Value(11) != "")
			RTF .= "\cf" Map.Keynames
		else if (Match.Value(12) != "")
			RTF .= "\cf" Map.Keywords
		else if (Match.Value(13) != "")
			RTF .= "\cf" Map.Functions
		else
			RTF .= "\cf" Map.Plain
		
		RTF .= " " EscapeRTF(Match.Value())
		Pos := FoundPos + Match.Len()
	}
	
	return Settings.Cache.RTFHeader . RTF
	. "\cf" Map.Plain " " EscapeRTF(SubStr(Code, Pos)) "\`n}"
}
class CodeQuickTester
{
	static Msftedit := DllCall("LoadLibrary", "Str", "Msftedit.dll")
	EditorString := """" A_AhkPath """ """ A_ScriptFullPath """ ""%1"""
	OrigEditorString := "notepad.exe %1"
	Title := "CodeQuickTester"
	
	__New(Settings)
	{
		this.Settings := Settings
		
		this.Shell := ComObjCreate("WScript.Shell")
		
		this.Bound := []
		this.Bound.RunButton := this.RunButton.Bind(this)
		this.Bound.GuiSize := this.GuiSize.Bind(this)
		this.Bound.OnMessage := this.OnMessage.Bind(this)
		this.Bound.UpdateStatusBar := this.UpdateStatusBar.Bind(this)
		this.Bound.UpdateAutoComplete := this.UpdateAutoComplete.Bind(this)
		this.Bound.CheckIfRunning := this.CheckIfRunning.Bind(this)
		this.Bound.Highlight := this.Highlight.Bind(this)
		this.Bound.SyncGutter := this.SyncGutter.Bind(this)
		
		Buttons := new this.MenuButtons(this)
		this.Bound.Indent := Buttons.Indent.Bind(Buttons)
		this.Bound.Unindent := Buttons.Unindent.Bind(Buttons)
		Menus :=
		( LTrim Join Comments
		[
			["&File", [
				["&Run`tF5", this.Bound.RunButton],
				[],
				["&New`tCtrl+N", Buttons.New.Bind(Buttons)],
				["&Open`tCtrl+O", Buttons.Open.Bind(Buttons)],
				["Open &Working Dir`tCtrl+Shift+O", Buttons.OpenFolder.Bind(Buttons)],
				["&Save`tCtrl+S", Buttons.Save.Bind(Buttons, False)],
				["&Save as`tCtrl+Shift+S", Buttons.Save.Bind(Buttons, True)],
				["Rename", Buttons.Rename.Bind(Buttons)],
				[],
				["&Publish", Buttons.Publish.Bind(Buttons)],
				["&Fetch", Buttons.Fetch.Bind(Buttons)],
				[],
				["E&xit`tCtrl+W", this.GuiClose.Bind(this)]
			]], ["&Edit", [
				["Find`tCtrl+F", Buttons.Find.Bind(Buttons)],
				[],
				["Comment Lines`tCtrl+K", Buttons.Comment.Bind(Buttons)],
				["Uncomment Lines`tCtrl+Shift+K", Buttons.Uncomment.Bind(Buttons)],
				[],
				["Indent Lines", this.Bound.Indent],
				["Unindent Lines", this.Bound.Unindent],
				[],
				["Include &Relative", Buttons.IncludeRel.Bind(Buttons)],
				["Include &Absolute", Buttons.IncludeAbs.Bind(Buttons)],
				[],
				["Script &Options", Buttons.ScriptOpts.Bind(Buttons)]
			]], ["&Tools", [
				["&Pastebin`tCtrl+P", Buttons.Paste.Bind(Buttons)],
				["Re&indent`tCtrl+I", Buttons.AutoIndent.Bind(Buttons)],
				[],
				["&AlwaysOnTop`tAlt+A", Buttons.ToggleOnTop.Bind(Buttons)],
				["Global Run Hotkeys", Buttons.GlobalRun.Bind(Buttons)],
				[],
				["Install Service Handler", Buttons.ServiceHandler.Bind(Buttons)],
				["Set as Default Editor", Buttons.DefaultEditor.Bind(Buttons)],
				[],
				["&Highlighter", Buttons.Highlighter.Bind(Buttons)],
				["AutoComplete", Buttons.AutoComplete.Bind(Buttons)]
			]], ["&Help", [
				["Open &Help File`tCtrl+H", Buttons.Help.Bind(Buttons)],
				["&About", Buttons.About.Bind(Buttons)]
			]]
		]
		)
		
		Gui, New, +Resize +hWndhMainWindow -AlwaysOnTop
		this.AlwaysOnTop := False
		this.hMainWindow := hMainWindow
		this.Menus := CreateMenus(Menus)
		Gui, Menu, % this.Menus[1]
		
		; If set as default, check the highlighter option
		if this.Settings.UseHighlighter
			Menu, % this.Menus[4], Check, &Highlighter
		
		; If set as default, check the global run hotkeys option
		if this.Settings.GlobalRun
			Menu, % this.Menus[4], Check, Global Run Hotkeys
		
		; If set as default, check the AutoComplete option
		if this.Settings.UseAutoComplete
			Menu, % this.Menus[4], Check, AutoComplete
		
		; If service handler is installed, check the menu option
		if ServiceHandler.Installed()
			Menu, % this.Menus[4], Check, Install Service Handler
		
		RegRead, Editor, HKCR, AutoHotkeyScript\Shell\Edit\Command
		if (Editor == this.EditorString)
			Menu, % this.Menus[4], Check, Set as Default Editor
		
		; Register for events
		WinEvents.Register(this.hMainWindow, this)
		for each, Msg in [0x111, 0x100, 0x101, 0x201, 0x202, 0x204] ; WM_COMMAND, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN
			OnMessage(Msg, this.Bound.OnMessage)
		
		; Add code editor and gutter for line numbers
		this.RichCode := new RichCode(this.Settings, "-E0x20000")
		RichEdit_AddMargins(this.RichCode.hWnd, 3, 3)
		if Settings.Gutter.Width
			this.AddGutter()
		
		if B_Params.HasKey(1)
			FilePath := RegExReplace(B_Params[1], "^ahk:") ; Remove leading service handler
		else
			FilePath := Settings.DefaultPath
		
		if (FilePath ~= "^https?://")
			this.RichCode.Value := UrlDownloadToVar(FilePath)
		else if (FilePath = "Clipboard")
			this.RichCode.Value := Clipboard
		else if InStr(FileExist(FilePath), "A")
		{
			this.RichCode.Value := FileOpen(FilePath, "r").Read()
			this.RichCode.Modified := False
			
			if (FilePath == Settings.DefaultPath)
			{
				; Place cursor after the default template text
				this.RichCode.Selection := [-1, -1]
			}
			else
			{
				; Keep track of the file currently being edited
				this.FilePath := GetFullPathName(FilePath)
				
				; Follow the directory of the most recently opened file
				SetWorkingDir, %FilePath%\..
			}
		}
		else
			this.RichCode.Value := ""
		
		if (this.FilePath == "")
			Menu, % this.Menus[2], Disable, Rename
		
		; 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, hWndhStatusBar
		this.UpdateStatusBar()
		ControlGetPos,,,, StatusBarHeight,, ahk_id %hStatusBar%
		this.StatusBarHeight := StatusBarHeight
		
		; Initialize the AutoComplete
		this.AC := new this.AutoComplete(this, this.settings.UseAutoComplete)
		
		this.UpdateTitle()
		Gui, Show, w640 h480
	}
	
	AddGutter()
	{
		s := this.Settings, f := s.Font, g := s.Gutter
		
		; Add the RichEdit control for the gutter
		Gui, Add, Custom, ClassRichEdit50W hWndhGutter +0x5031b1c6 -HScroll -VScroll
		this.hGutter := hGutter
		
		; Set the background and font settings
		FGColor := RichCode.BGRFromRGB(g.FGColor)
		BGColor := RichCode.BGRFromRGB(g.BGColor)
		VarSetCapacity(CF2, 116, 0)
		NumPut(116,        &CF2+ 0, "UInt") ; cbSize      = sizeof(CF2)
		NumPut(0xE<<28,    &CF2+ 4, "UInt") ; dwMask      = CFM_COLOR|CFM_FACE|CFM_SIZE
		NumPut(f.Size*20,  &CF2+12, "UInt") ; yHeight     = twips
		NumPut(FGColor,    &CF2+20, "UInt") ; crTextColor = 0xBBGGRR
		StrPut(f.Typeface, &CF2+26, 32, "UTF-16") ; szFaceName = TCHAR
		SendMessage(0x444, 0, &CF2,    hGutter) ; EM_SETCHARFORMAT
		SendMessage(0x443, 0, BGColor, hGutter) ; EM_SETBKGNDCOLOR
		
		RichEdit_AddMargins(hGutter, 3, 3, -3, 0)
	}
	
	RunButton()
	{
		if (this.Exec.Status == 0) ; Running
			this.Exec.Terminate() ; CheckIfRunning updates the GUI
		else ; Not running or doesn't exist
		{
			this.Exec := ExecScript(this.RichCode.Value
			, this.Settings.Params
			, this.Settings.AhkPath)
			
			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, FilePath:="")
	{
		; Do nothing if nothing is changing
		if (this.FilePath == FilePath && this.RichCode.Value == Code)
			return
		
		; Confirm the user really wants to load new code
		Gui, +OwnDialogs
		MsgBox, 308, % this.Title " - Confirm Overwrite"
		, Are you sure you want to overwrite your code?
		IfMsgBox, No
			return
		
		; If we're changing the open file mark as modified
		; If we're loading a new file mark as unmodified
		this.RichCode.Modified := this.FilePath == FilePath
		this.FilePath := FilePath
		if (this.FilePath == "")
			Menu, % this.Menus[2], Disable, Rename
		else
			Menu, % this.Menus[2], Enable, Rename
		
		; Update the GUI
		this.RichCode.Value := Code
		this.UpdateStatusBar()
	}
	
	OnMessage(wParam, lParam, Msg, hWnd)
	{
		if (hWnd == this.hMainWindow && Msg == 0x111 ; WM_COMMAND
			&& lParam == this.RichCode.hWnd)         ; for RichEdit
		{
			Command := wParam >> 16
			
			if (Command == 0x400) ; An event that fires on scroll
			{
				this.SyncGutter()
				
				; If the user is scrolling too fast it can cause some messages
				; to be dropped. Set a timer to make sure that when the user stops
				; scrolling that the line numbers will be in sync.
				SetTimer(this.Bound.SyncGutter, -50)
			}
			else if (Command == 0x200) ; EN_KILLFOCUS
				if this.Settings.UseAutoComplete
					this.AC.Fragment := ""
		}
		else if (hWnd == this.RichCode.hWnd)
		{
			; Call UpdateStatusBar after the edit handles the keystroke
			SetTimer(this.Bound.UpdateStatusBar, -0)
			
			if this.Settings.UseAutoComplete
			{
				SetTimer(this.Bound.UpdateAutoComplete
				, -Abs(this.Settings.ACListRebuildDelay))
				
				if (Msg == 0x100) ; WM_KEYDOWN
					return this.AC.WM_KEYDOWN(wParam, lParam)
				else if (Msg == 0x201) ; WM_LBUTTONDOWN
					this.AC.Fragment := ""
			}
		}
		else if (hWnd == this.hGutter
			&& {0x100:1,0x101:1,0x201:1,0x202:1,0x204:1}[Msg]) ; WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN
		{
			; Disallow interaction with the gutter
			return True
		}
	}
	
	SyncGutter()
	{
		static BUFF, _ := VarSetCapacity(BUFF, 16, 0)
		
		if !this.Settings.Gutter.Width
			return
		
		SendMessage(0x4E0, &BUFF, &BUFF+4, this.RichCode.hwnd) ; EM_GETZOOM
		SendMessage(0x4DD, 0, &BUFF+8, this.RichCode.hwnd)     ; EM_GETSCROLLPOS
		
		; Don't update the gutter unnecessarily
		State := NumGet(BUFF, 0, "UInt") . NumGet(BUFF, 4, "UInt")
		. NumGet(BUFF, 8, "UInt") . NumGet(BUFF, 12, "UInt")
		if (State == this.GutterState)
			return
		
		NumPut(-1, BUFF, 8, "UInt") ; Don't sync horizontal position
		Zoom := [NumGet(BUFF, "UInt"), NumGet(BUFF, 4, "UInt")]
		PostMessage(0x4E1, Zoom[1], Zoom[2], this.hGutter)     ; EM_SETZOOM
		PostMessage(0x4DE, 0, &BUFF+8, this.hGutter)           ; EM_SETSCROLLPOS
		this.ZoomLevel := Zoom[1] / Zoom[2]
		if (this.ZoomLevel != this.LastZoomLevel)
			SetTimer(this.Bound.GuiSize, -0), this.LastZoomLevel := this.ZoomLevel
		
		this.GutterState := State
	}
	
	GetKeywordFromCaret()
	{
		; https://autohotkey.com/boards/viewtopic.php?p=180369#p180369
		static Buffer
		IsUnicode := !!A_IsUnicode
		
		rc := this.RichCode
		sel := rc.Selection
		
		; Get the currently selected line
		LineNum := rc.SendMsg(0x436, 0, sel[1]) ; EM_EXLINEFROMCHAR
		
		; Size a buffer according to the line's length
		Length := rc.SendMsg(0xC1, sel[1], 0) ; EM_LINELENGTH
		VarSetCapacity(Buffer, Length << !!A_IsUnicode, 0)
		NumPut(Length, Buffer, "UShort")
		
		; Get the text from the line
		rc.SendMsg(0xC4, LineNum, &Buffer) ; EM_GETLINE
		lineText := StrGet(&Buffer, Length)
		
		; Parse the line to find the word
		LineIndex := rc.SendMsg(0xBB, LineNum, 0) ; EM_LINEINDEX
		RegExMatch(SubStr(lineText, 1, sel[1]-LineIndex), "[#\w]+$", Start)
		RegExMatch(SubStr(lineText, sel[1]-LineIndex+1), "^[#\w]+", End)
		
		return Start . End
	}
	
	UpdateStatusBar()
	{
		; Delete the timer if it was called by one
		SetTimer(this.Bound.UpdateStatusBar, "Delete")
		
		; Get the document length and cursor position
		VarSetCapacity(GTL, 8, 0), NumPut(1200, GTL, 4, "UInt")
		Len := this.RichCode.SendMsg(0x45F, &GTL, 0) ; EM_GETTEXTLENGTHEX (Handles newlines better than GuiControlGet on RE)
		ControlGet, Row, CurrentLine,,, % "ahk_id" this.RichCode.hWnd
		ControlGet, Col, CurrentCol,,, % "ahk_id" this.RichCode.hWnd
		
		; Get Selected Text Length
		; If the user has selected 1 char further than the end of the document,
		; which is allowed in a RichEdit control, subtract 1 from the length
		Sel := this.RichCode.Selection
		Sel := Sel[2] - Sel[1] - (Sel[2] > Len)
		
		; Get the syntax tip, if any
		if (SyntaxTip := HelpFile.GetSyntax(this.GetKeywordFromCaret()))
			this.SyntaxTip := SyntaxTip
		
		; Update the Status Bar text
		Gui, % this.hMainWindow ":Default"
		SB_SetText("Len " Len ", Line " Row ", Col " Col
		. (Sel > 0 ? ", Sel " Sel : "") "     " this.SyntaxTip)
		
		; Update the title Bar
		this.UpdateTitle()
		
		; Update the gutter to match the document
		if this.Settings.Gutter.Width
		{
			ControlGet, Lines, LineCount,,, % "ahk_id" this.RichCode.hWnd
			if (Lines != this.LineCount)
			{
				Loop, %Lines%
					Text .= A_Index "`n"
				GuiControl,, % this.hGutter, %Text%
				this.SyncGutter()
				this.LineCount := Lines
			}
		}
	}
	
	UpdateTitle()
	{
		Title := this.Title
		
		; Show the current file name
		if this.FilePath
		{
			SplitPath, % this.FilePath, FileName
			Title .= " - " FileName
		}
		
		; Show the curernt modification status
		if this.RichCode.Modified
			Title .= "*"
		
		; Return if the title doesn't need to be updated
		if (Title == this.VisibleTitle)
			return
		this.VisibleTitle := Title
		
		HiddenWindows := A_DetectHiddenWindows
		DetectHiddenWindows, On
		WinSetTitle, % "ahk_id" this.hMainWindow,, %Title%
		DetectHiddenWindows, %HiddenWindows%
	}
	
	UpdateAutoComplete()
	{
		; Delete the timer if it was called by one
		SetTimer(this.Bound.UpdateAutoComplete, "Delete")
		
		this.AC.BuildWordList()
	}
	
	RegisterCloseCallback(CloseCallback)
	{
		this.CloseCallback := CloseCallback
	}
	
	GuiSize()
	{
		static RECT, _ := VarSetCapacity(RECT, 16, 0)
		if A_Gui
			gw := A_GuiWidth, gh := A_GuiHeight
		else
		{
			DllCall("GetClientRect", "UPtr", this.hMainWindow, "Ptr", &RECT, "UInt")
			gw := NumGet(RECT, 8, "Int"), gh := NumGet(RECT, 12, "Int")
		}
		gtw := 3 + Round(this.Settings.Gutter.Width) * (this.ZoomLevel ? this.ZoomLevel : 1), sbh := this.StatusBarHeight
		GuiControl, Move, % this.RichCode.hWnd, % "x" 0+gtw "y" 0         "w" gw-gtw "h" gh-28-sbh
		if this.Settings.Gutter.Width
			GuiControl, Move, % this.hGutter  , % "x" 0     "y" 0         "w" gtw    "h" gh-28-sbh
		GuiControl, Move, % this.hRunButton   , % "x" 0     "y" gh-28-sbh "w" gw     "h" 28
	}
	
	GuiDropFiles(hWnd, Files)
	{
		; TODO: support multiple file drop
		this.LoadCode(FileOpen(Files[1], "r").Read(), Files[1])
	}
	
	GuiClose()
	{
		if this.RichCode.Modified
		{
			Gui, +OwnDialogs
			MsgBox, 308, % this.Title " - Confirm Exit", There are unsaved changes. Are you sure you want to exit?
			IfMsgBox, No
				return true
		}
		
		if (this.Exec.Status == 0) ; Running
		{
			SetTimer(this.Bound.CheckIfRunning, "Delete")
			this.Exec.Terminate()
		}
		
		; Free up the AC class
		this.AC := ""
		
		; Release 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)
		
		; Delete timers
		SetTimer(this.Bound.SyncGutter, "Delete")
		SetTimer(this.Bound.GuiSize, "Delete")
		
		; Break all the BoundFunc circular references
		this.Delete("Bound")
		
		; Release WinEvents handler
		WinEvents.Unregister(this.hMainWindow)
		
		; Release GUI window and control glabels
		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
	{
		static Targets := {"IRC": "#ahk", "Discord": "discord"}
		
		__New(Parent)
		{
			this.Parent := Parent
			
			ParentWnd := this.Parent.hMainWindow
			Gui, New, +Owner%ParentWnd% +ToolWindow +hWndhWnd
			this.hWnd := hWnd
			Gui, Margin, 5, 5
			
			Gui, Add, Text, xm ym w30 h22 +0x200, Desc: ; 0x200 for vcenter
			Gui, Add, Edit, x+5 yp w125 h22 hWndhPasteDesc, % this.Parent.Settings.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.Settings.DefaultName
			this.hPasteName := hPasteName
			
			Gui, Add, DropDownList, x+5 yp w75 hWndhPasteChan, Announce||IRC|Discord
			this.hPasteChan := hPasteChan
			
			PostMessage, 0x153, -1, 22-6,, ahk_id %hPasteChan% ; Set height of ComboBox
			Gui, Show,, % this.Parent.Title " - Pastebin"
			
			WinEvents.Register(this.hWnd, this)
		}
		
		GuiClose()
		{
			GuiControl, -g, % this.hPasteButton
			WinEvents.Unregister(this.hWnd)
			Gui, Destroy
		}
		
		GuiEscape()
		{
			this.GuiClose()
		}
		
		Paste()
		{
			GuiControlGet, PasteDesc,, % this.hPasteDesc
			GuiControlGet, PasteName,, % this.hPasteName
			GuiControlGet, PasteChan,, % this.hPasteChan
			this.GuiClose()
			
			Link := Ahkbin(this.Parent.RichCode.Value, PasteName, PasteDesc, this.Targets[PasteChan])
			
			MsgBox, 292, % this.Parent.Title " - Pasted", Link received:`n%Link%`n`nCopy to clipboard?
			IfMsgBox, Yes
				Clipboard := Link
		}
	}
	class Publish
	{
		__New(Parent)
		{
			this.Parent := Parent
			
			ParentWnd := this.Parent.hMainWindow
			Gui, New, +Owner%ParentWnd% +ToolWindow +hWndhWnd
			this.hWnd := hWnd
			Gui, Margin, 5, 5
			
			; 0x200 for vcenter
			Gui, Add, Text, w245 h22 Center +0x200, Gather all includes and save to file.
			
			Gui, Add, Checkbox, hWndhWnd w120 h22 Checked Section, Keep Comments
			this.hComments := hWnd
			Gui, Add, Checkbox, hWndhWnd w120 h22 Checked, Keep Indentation
			this.hIndent := hWnd
			Gui, Add, Checkbox, hWndhWnd w120 h22 Checked, Keep Empty Lines
			this.hEmpties := hWnd
			
			Gui, Add, Button, hWndhWnd w120 h81 ys-1 Default, Export
			this.hButton := hWnd
			BoundPublish := this.Publish.Bind(this)
			GuiControl, +g, %hWnd%, %BoundPublish%
			
			Gui, Show,, % this.Parent.Title " - Publish"
			
			WinEvents.Register(this.hWnd, this)
		}
		
		GuiClose()
		{
			GuiControl, -g, % this.hButton
			WinEvents.Unregister(this.hWnd)
			Gui, Destroy
		}
		
		GuiEscape()
		{
			this.GuiClose()
		}
		
		Publish()
		{
			GuiControlGet, KeepComments,, % this.hComments
			GuiControlGet, KeepIndent,, % this.hIndent
			GuiControlGet, KeepEmpties,, % this.hEmpties
			this.GuiClose()
			
			Gui, % this.Parent.hMainWindow ":+OwnDialogs"
			FileSelectFile, FilePath, S18,, % this.Parent.Title " - Publish Code"
			if ErrorLevel
				return
			
			FileOpen(FilePath, "w").Write(this.Parent.RichCode.Value)
			PreprocessScript(Text, FilePath, KeepComments, KeepIndent, KeepEmpties)
			FileOpen(FilePath, "w").Write(Text)
		}
	}
	class Find
	{
		__New(Parent)
		{
			this.Parent := Parent
			
			ParentWnd := this.Parent.hMainWindow
			Gui, New, +Owner%ParentWnd% +ToolWindow +hWndhWnd
			this.hWnd := hWnd
			Gui, Margin, 5, 5
			
			
			; Search
			Gui, Add, Edit, hWndhWnd w200
			SendMessage, 0x1501, True, &cue := "Search Text",, ahk_id %hWnd% ; EM_SETCUEBANNER
			this.hNeedle := hWnd
			
			Gui, Add, Button, yp-1 x+m w75 Default hWndhWnd, Find Next
			Bound := this.BtnFind.Bind(this)
			GuiControl, +g, %hWnd%, %Bound%
			
			Gui, Add, Button, yp x+m w75 hWndhWnd, Coun&t All
			Bound := this.BtnCount.Bind(this)
			GuiControl, +g, %hWnd%, %Bound%
			
			
			; Replace
			Gui, Add, Edit, hWndhWnd w200 xm Section
			SendMessage, 0x1501, True, &cue := "Replacement",, ahk_id %hWnd% ; EM_SETCUEBANNER
			this.hReplace := hWnd
			
			Gui, Add, Button, yp-1 x+m w75 hWndhWnd, &Replace
			Bound := this.Replace.Bind(this)
			GuiControl, +g, %hWnd%, %Bound%
			
			Gui, Add, Button, yp x+m w75 hWndhWnd, Replace &All
			Bound := this.ReplaceAll.Bind(this)
			GuiControl, +g, %hWnd%, %Bound%
			
			
			; Options
			Gui, Add, Checkbox, hWndhWnd xm, &Case Sensitive
			this.hOptCase := hWnd
			Gui, Add, Checkbox, hWndhWnd, Re&gular Expressions
			this.hOptRegEx := hWnd
			Gui, Add, Checkbox, hWndhWnd, Transform`, &Deref
			this.hOptDeref := hWnd
			
			
			Gui, Show,, % this.Parent.Title " - Find"
			
			WinEvents.Register(this.hWnd, this)
		}
		
		GuiClose()
		{
			GuiControl, -g, % this.hButton
			WinEvents.Unregister(this.hWnd)
			Gui, Destroy
		}
		
		GuiEscape()
		{
			this.GuiClose()
		}
		
		GetNeedle()
		{
			Opts := this.Case ? "`n" : "i`n"
			Opts .= this.Needle ~= "^[^\(]\)" ? "" : ")"
			if this.RegEx
				return Opts . this.Needle
			else
				return Opts "\Q" StrReplace(this.Needle, "\E", "\E\\E\Q") "\E"
		}
		
		Find(StartingPos:=1, WrapAround:=True)
		{
			Needle := this.GetNeedle()
			
			; Search from StartingPos
			NextPos := RegExMatch(this.Haystack, Needle, Match, StartingPos)
			
			; Search from the top
			if (!NextPos && WrapAround)
				NextPos := RegExMatch(this.Haystack, Needle, Match)
			
			return NextPos ? [NextPos, NextPos+StrLen(Match)] : False
		}
		
		Submit()
		{
			; Options
			GuiControlGet, Deref,, % this.hOptDeref
			GuiControlGet, Case,, % this.hOptCase
			this.Case := Case
			GuiControlGet, RegEx,, % this.hOptRegEx
			this.RegEx := RegEx
			
			; Search Text/Needle
			GuiControlGet, Needle,, % this.hNeedle
			if Deref
				Transform, Needle, Deref, %Needle%
			this.Needle := Needle
			
			; Replacement
			GuiControlGet, Replace,, % this.hReplace
			if Deref
				Transform, Replace, Deref, %Replace%
			this.Replace := Replace
			
			; Haystack
			this.Haystack := StrReplace(this.Parent.RichCode.Value, "`r")
		}
		
		BtnFind()
		{
			Gui, +OwnDialogs
			this.Submit()
			
			; Find and select the item or error out
			if (Pos := this.Find(this.Parent.RichCode.Selection[1]+2))
				this.Parent.RichCode.Selection := [Pos[1] - 1, Pos[2] - 1]
			else
				MsgBox, 0x30, % this.Parent.Title " - Find", Search text not found
		}
		
		BtnCount()
		{
			Gui, +OwnDialogs
			this.Submit()
			
			; Find and count all instances
			Count := 0, Start := 1
			while (Pos := this.Find(Start, False))
				Start := Pos[1]+1, Count += 1
			
			MsgBox, 0x40, % this.Parent.Title " - Find", %Count% instances found
		}
		
		Replace()
		{
			this.Submit()
			
			; Get the current selection
			Sel := this.Parent.RichCode.Selection
			
			; Find the next occurrence including the current selection
			Pos := this.Find(Sel[1]+1)
			
			; If the found item is already selected
			if (Sel[1]+1 == Pos[1] && Sel[2]+1 == Pos[2])
			{
				; Replace it
				this.Parent.RichCode.SelectedText := this.Replace
				
				; Update the haystack to include the replacement
				this.Haystack := StrReplace(this.Parent.RichCode.Value, "`r")
				
				; Find the next item *not* including the current selection
				Pos := this.Find(Sel[1]+StrLen(this.Replace)+1)
			}
			
			; Select the next found item or error out
			if Pos
				this.Parent.RichCode.Selection := [Pos[1] - 1, Pos[2] - 1]
			else
				MsgBox, 0x30, % this.Parent.Title " - Find", No more instances found
		}
		
		ReplaceAll()
		{
			rc := this.Parent.RichCode
			this.Submit()
			
			Needle := this.GetNeedle()
			
			; Replace the text in a way that pushes to the undo buffer
			rc.Frozen := True
			Sel := rc.Selection
			rc.Selection := [0, -1]
			rc.SelectedText := RegExReplace(this.Haystack, Needle, this.Replace, Count)
			rc.Selection := Sel
			rc.Frozen := False
			
			MsgBox, 0x40, % this.Parent.Title " - Find", %Count% instances replaced
		}
	}
	class ScriptOpts
	{
		__New(Parent)
		{
			this.Parent := Parent
			
			; Create a GUI
			ParentWnd := this.Parent.hMainWindow
			Gui, New, +Owner%ParentWnd% +ToolWindow +hWndhWnd
			this.hWnd := hWnd
			WinEvents.Register(this.hWnd, this)
			
			; Add path picker button
			Gui, Add, Button, xm ym w95 hWndhButton, Pick AHK Path
			BoundSelectFile := this.SelectFile.Bind(this)
			GuiControl, +g, %hButton%, %BoundSelectFile%
			
			; Add path visualization field
			Gui, Add, Edit, ym w250 ReadOnly hWndhAhkPath, % this.Parent.Settings.AhkPath
			this.hAhkPath := hAhkPath
			
			; Add parameters field
			Gui, Add, Text, xm w95 h22 +0x200, Parameters:
			Gui, Add, Edit, yp x+m w250 hWndhParamEdit
			this.hParamEdit := hParamEdit
			ParamEditBound := this.ParamEdit.Bind(this)
			GuiControl, +g, %hParamEdit%, %ParamEditBound%
			
			; Add Working Directory field
			Gui, Add, Button, xm w95 hWndhWDButton, Pick Working Dir
			BoundSelectPath := this.SelectPath.Bind(this)
			GuiControl, +g, %hWDButton%, %BoundSelectPath%
			
			; Add Working Dir visualization field
			Gui, Add, Edit, x+m w250 ReadOnly hWndhWorkingDir, %A_WorkingDir%
			this.hWorkingDir := hWorkingDir
			
			; Show the GUI
			Gui, Show,, % this.Parent.Title " - Script Options"
		}
		
		ParamEdit()
		{
			GuiControlGet, ParamEdit,, % this.hParamEdit
			this.Parent.Settings.Params := ParamEdit
		}
		
		SelectFile()
		{
			GuiControlGet, AhkPath,, % this.hAhkPath
			FileSelectFile, AhkPath, 1, %AhkPath%, Pick an AHK EXE, Executables (*.exe)
			if !AhkPath
				return
			this.Parent.Settings.AhkPath := AhkPath
			GuiControl,, % this.hAhkPath, %AhkPath%
		}
		
		SelectPath()
		{
			FileSelectFolder, WorkingDir, *%A_WorkingDir%, 0, Choose the Working Directory
			if !WorkingDir
				return
			SetWorkingDir, %WorkingDir%
			this.UpdateFields()
		}
		
		UpdateFields()
		{
			GuiControl,, % this.hWorkingDir, %A_WorkingDir%
		}
		
		GuiClose()
		{
			WinEvents.Unregister(this.hWnd)
			Gui, Destroy
		}
		
		GuiEscape()
		{
			this.GuiClose()
		}
	}
	class MenuButtons
	{
		__New(Parent)
		{
			this.Parent := Parent
		}
		
		Save(SaveAs)
		{
			if (SaveAs || !this.Parent.FilePath)
			{
				Gui, +OwnDialogs
				FileSelectFile, FilePath, S18,, % this.Parent.Title " - Save Code"
				if ErrorLevel
					return
				this.Parent.FilePath := FilePath
			}
			
			FileOpen(this.Parent.FilePath, "w").Write(this.Parent.RichCode.Value)
			
			this.Parent.RichCode.Modified := False
			this.Parent.UpdateStatusBar()
		}
		
		Rename()
		{
			; Make sure the opened file still exists
			if !InStr(FileExist(this.Parent.FilePath), "A")
				throw Exception("Opened file no longer exists")
			
			; Ask where to move it to
			FileSelectFile, FilePath, S10, % this.Parent.FilePath
			, Rename As, AutoHotkey Scripts (*.ahk)
			if InStr(FileExist(FilePath), "A")
				throw Exception("Destination file already exists")
			
			; Attempt to move it
			FileMove, % this.Parent.FilePath, % FilePath
			if ErrorLevel
				throw Exception("Failed to rename file")
			this.Parent.FilePath := FilePath
		}
		
		Open()
		{
			Gui, +OwnDialogs
			FileSelectFile, FilePath, 3,, % this.Parent.Title " - Open Code"
			if ErrorLevel
				return
			this.Parent.LoadCode(FileOpen(FilePath, "r").Read(), FilePath)
			
			; Follow the directory of the most recently opened file
			SetWorkingDir, %FilePath%\..
			this.Parent.ScriptOpts.UpdateFields()
		}
		
		OpenFolder()
		{
			Run, explorer.exe "%A_WorkingDir%"
		}
		
		New()
		{
			Run, "%A_AhkPath%" "%A_ScriptFullPath%"
		}
		
		Publish()
		{ ; TODO: Recycle PubInstance
			if WinExist("ahk_id" this.PubInstance.hWnd)
				WinActivate, % "ahk_id" this.PubInstance.hWnd
			else
				this.PubInstance := new this.Parent.Publish(this.Parent)
		}
		
		Fetch()
		{
			Gui, +OwnDialogs
			InputBox, Url, % this.Parent.Title " - Fetch Code", Enter a URL to fetch code from.
			if (Url := Trim(Url))
				this.Parent.LoadCode(UrlDownloadToVar(Url))
		}
		
		Find()
		{ ; TODO: Recycle FindInstance
			if WinExist("ahk_id" this.FindInstance.hWnd)
				WinActivate, % "ahk_id" this.FindInstance.hWnd
			else
				this.FindInstance := new this.Parent.Find(this.Parent)
		}
		
		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)
		}
		
		ScriptOpts()
		{
			if WinExist("ahk_id" this.Parent.ScriptOptsInstance.hWnd)
				WinActivate, % "ahk_id" this.Parent.ScriptOptsInstance.hWnd
			else
				this.Parent.ScriptOptsInstance := new this.Parent.ScriptOpts(this.Parent)
		}
		
		ToggleOnTop()
		{
			if (this.Parent.AlwaysOnTop := !this.Parent.AlwaysOnTop)
			{
				Menu, % this.Parent.Menus[4], Check, &AlwaysOnTop`tAlt+A
				Gui, +AlwaysOnTop
			}
			else
			{
				Menu, % this.Parent.Menus[4], Uncheck, &AlwaysOnTop`tAlt+A
				Gui, -AlwaysOnTop
			}
		}
		
		Highlighter()
		{
			if (this.Parent.Settings.UseHighlighter := !this.Parent.Settings.UseHighlighter)
				Menu, % this.Parent.Menus[4], Check, &Highlighter
			else
				Menu, % this.Parent.Menus[4], Uncheck, &Highlighter
			
			; Force refresh the code, adding/removing any highlighting
			this.Parent.RichCode.Value := this.Parent.RichCode.Value
		}
		
		GlobalRun()
		{
			if (this.Parent.Settings.GlobalRun := !this.Parent.Settings.GlobalRun)
				Menu, % this.Parent.Menus[4], Check, Global Run Hotkeys
			else
				Menu, % this.Parent.Menus[4], Uncheck, Global Run Hotkeys
		}
		
		AutoIndent()
		{
			this.Parent.LoadCode(AutoIndent(this.Parent.RichCode.Value
			, this.Parent.Settings.Indent), this.Parent.FilePath)
		}
		
		Help()
		{
			HelpFile.Open(this.Parent.GetKeywordFromCaret())
		}
		
		About()
		{
			Gui, +OwnDialogs
			MsgBox,, % this.Parent.Title " - About", CodeQuickTester written by GeekDude
		}
		
		ServiceHandler()
		{
			Gui, +OwnDialogs
			if ServiceHandler.Installed()
			{
				MsgBox, 36, % this.Parent.Title " - Uninstall Service Handler"
				, Are you sure you want to remove CodeQuickTester from being the default service handler for "ahk:" links?
				IfMsgBox, Yes
				{
					ServiceHandler.Remove()
					Menu, % this.Parent.Menus[4], Uncheck, Install Service Handler
				}
			}
			else
			{
				MsgBox, 36, % this.Parent.Title " - Install Service Handler"
				, Are you sure you want to install CodeQuickTester as the default service handler for "ahk:" links?
				IfMsgBox, Yes
				{
					ServiceHandler.Install()
					Menu, % this.Parent.Menus[4], Check, Install Service Handler
				}
			}
		}
		
		DefaultEditor()
		{
			Gui, +OwnDialogs
			
			if !A_IsAdmin
			{
				MsgBox, 48, % this.Parent.Title " - Change Editor", You must be running as administrator to use this feature.
				return
			}
			
			RegRead, Editor, HKCR, AutoHotkeyScript\Shell\Edit\Command
			if (Editor == this.Parent.EditorString)
			{
				MsgBox, 36, % this.Parent.Title " - Remove as Default Editor"
				, % "Are you sure you want to restore the original default editor for .ahk files?"
				. "`n`n" this.Parent.OrigEditorString
				IfMsgBox, Yes
				{
					RegWrite REG_SZ, HKCR, AutoHotkeyScript\Shell\Edit\Command,, % this.Parent.OrigEditorString
					Menu, % this.Parent.Menus[4], Uncheck, Set as Default Editor
				}
			}
			else
			{
				MsgBox, 36, % this.Parent.Title " - Set as Default Editor"
				, % "Are you sure you want to install CodeQuickTester as the default editor for .ahk files?"
				. "`n`n" this.Parent.EditorString
				IfMsgBox, Yes
				{
					RegWrite REG_SZ, HKCR, AutoHotkeyScript\Shell\Edit\Command,, % this.Parent.EditorString
					MsgBox, %ErrorLevel%
					Menu, % this.Parent.Menus[4], Check, Set as Default Editor
				}
			}
			
			
		}
		
		Comment()
		{
			this.Parent.RichCode.IndentSelection(False, ";")
		}
		
		Uncomment()
		{
			this.Parent.RichCode.IndentSelection(True, ";")
		}
		
		Indent()
		{
			this.Parent.RichCode.IndentSelection()
		}
		
		Unindent()
		{
			this.Parent.RichCode.IndentSelection(True)
		}
		
		IncludeRel()
		{
			FileSelectFile, AbsPath, 1,, Pick a script to include, AutoHotkey Scripts (*.ahk)
			if ErrorLevel
				return
			
			; Get the relative path
			VarSetCapacity(RelPath, A_IsUnicode?520:260) ; MAX_PATH
			if !DllCall("Shlwapi.dll\PathRelativePathTo"
				, "Str", RelPath                    ; Out Directory
				, "Str", A_WorkingDir, "UInt", 0x10 ; From Directory
				, "Str", AbsPath, "UInt", 0x10)     ; To Directory
				throw Exception("Relative path could not be found")
			
			; Select the start of the line
			RC := this.Parent.RichCode
			Top := RC.SendMsg(0x436, 0, RC.Selection[1]) ; EM_EXLINEFROMCHAR
			TopLineIndex := RC.SendMsg(0xBB, Top, 0) ; EM_LINEINDEX
			RC.Selection := [TopLineIndex, TopLineIndex]
			
			; Insert the include
			RC.SelectedText := "#Include " RelPath "`n"
			RC.Selection[1] := RC.Selection[2]
		}
		
		IncludeAbs()
		{
			FileSelectFile, AbsPath, 1,, Pick a script to include, AutoHotkey Scripts (*.ahk)
			if ErrorLevel
				return
			
			; Select the start of the line
			RC := this.Parent.RichCode
			Top := RC.SendMsg(0x436, 0, RC.Selection[1]) ; EM_EXLINEFROMCHAR
			TopLineIndex := RC.SendMsg(0xBB, Top, 0) ; EM_LINEINDEX
			RC.Selection := [TopLineIndex, TopLineIndex]
			
			; Insert the include
			RC.SelectedText := "#Include " AbsPath "`n"
			RC.Selection[1] := RC.Selection[2]
		}
		
		AutoComplete()
		{
			if (this.Parent.Settings.UseAutoComplete := !this.Parent.Settings.UseAutoComplete)
				Menu, % this.Parent.Menus[4], Check, AutoComplete
			else
				Menu, % this.Parent.Menus[4], Uncheck, AutoComplete
			
			this.Parent.AC.Enabled := this.Parent.Settings.UseAutoComplete
		}
	}
	/*
		Implements functionality necessary for AutoCompletion of keywords in the
		RichCode control. Currently works off of values stored in the provided
		Parent object, but could be modified to work off a provided RichCode
		instance directly.
		
		The class is mostly self contained and could be easily extended to other
		projects, and even other types of controls. The main method of interacting
		with the class is by passing it WM_KEYDOWN messages. Another way to interact
		is by modifying the Fragment property, especially to clear it when you want
		to cancel autocompletion.
		
		Depends on CQT.ahk, and optionally on HelpFile.ahk
	*/
	
	class AutoComplete
	{
		; Maximum number of suggestions to be displayed in the dialog
		static MaxSuggestions := 9
		
		; Minimum length for a word to be entered into the word list
		static MinWordLen := 4
		
		; Minimum length of fragment before suggestions should be displayed
		static MinSuggestLen := 3
		
		; Stores the initial caret position for newly typed fragments
		static CaretX := 0, CaretY := 0
		
		
		; --- Properties ---
		
		Fragment[]
		{
			get
			{
				return this._Fragment
			}
			
			set
			{
				this._Fragment := Value
				
				; Give suggestions when a fragment of sufficient
				; length has been provided
				if (StrLen(this._Fragment) >= 3)
					this._Suggest()
				else
					this._Hide()
				
				return this._Fragment
			}
		}
		
		Enabled[]
		{
			get
			{
				return this._Enabled
			}
			
			set
			{
				this._Enabled := Value
				if (Value)
					this.BuildWordList()
				else
					this.Fragment := ""
				return Value
			}
		}
		
		
		; --- Constructor, Destructor ---
		
		__New(Parent, Enabled:=True)
		{
			this.Parent := Parent
			this.Enabled := Enabled
			this.WineVer := DllCall("ntdll.dll\wine_get_version", "AStr")
			
			; Create the tool GUI for the floating list
			hParentWnd := this.Parent.hMainWindow
			Gui, +hWndhDefaultWnd
			Relation := this.WineVer ? "Parent" Parent.RichCode.hWnd : "Owner" Parent.hMainWindow
			Gui, New, +%Relation% -Caption +ToolWindow +hWndhWnd
			this.hWnd := hWnd
			Gui, Margin, 0, 0
			
			; Create the ListBox control withe appropriate font and styling
			Font := this.Parent.Settings.Font
			Gui, Font, % "s" Font.Size, % Font.Typeface
			Gui, Add, ListBox, x0 y0 r1 0x100 AltSubmit hWndhListBox, Item
			this.hListBox := hListBox
			
			; Finish GUI creation and restore the default GUI
			Gui, Show, Hide, % this.Parent.Title " - AutoComplete"
			Gui, %hDefaultWnd%:Default
			
			; Get relevant dimensions of the ListBox for later resizing
			SendMessage, 0x1A1, 0, 0,, % "ahk_id" this.hListBox ; LB_GETITEMHEIGHT
			this.ListBoxItemHeight := ErrorLevel
			VarSetCapacity(ListBoxRect, 16, 0)
			DllCall("User32.dll\GetClientRect", "Ptr", this.hListBox, "Ptr", &ListBoxRect)
			this.ListBoxMargins := NumGet(ListBoxRect, 12, "Int") - this.ListBoxItemHeight
			
			; Set up the GDI Device Context for later text measurement in _GetWidth
			this.hDC := DllCall("GetDC", "UPtr", this.hListBox, "UPtr")
			SendMessage, 0x31, 0, 0,, % "ahk_id" this.hListBox ; WM_GETFONT
			this.hFont := DllCall("SelectObject", "UPtr", this.hDC, "UPtr", ErrorLevel, "UPtr")
			
			; Record the total screen width for later user. If the monitors get
			; rearranged while the script is still running this value will be
			; inaccurate. However, this will not likely be a significant issue,
			; and the issues caused by it would be minimal.
			SysGet, ScreenWidth, 78
			this.ScreenWidth := ScreenWidth
			
			; Pull a list of default words from the help file.
			; TODO: Include some kind of hard-coded list for when the help file is
			;       not present, or to supplement the help file.
			for Key in HelpFile.GetLookup()
				this.DefaultWordList .= "|" LTrim(Key, "#")
			
			; Build the initial word list based on the default words and the
			; RichCode's contents at the time of AutoComplete's initialization
			this.BuildWordList()
		}
		
		__Delete()
		{
			Gui, % this.hWnd ":Destroy"
			this.Visible := False
			DllCall("SelectObject", "UPtr", this.hDC, "UPtr", this.hFont, "UPtr")
			DllCall("ReleaseDC", "UPtr", this.hListBox, "UPtr", this.hDC)
		}
		
		
		; --- Private Methods ---
		
		; Gets the pixel-based width of a provided text snippet using the GDI font
		; selected into the ListBox control
		_GetWidth(Text)
		{
			MaxWidth := 0
			Loop, Parse, Text, |
			{
				DllCall("GetTextExtentPoint32", "UPtr", this.hDC, "Str", A_LoopField
				, "Int", StrLen(A_LoopField), "Int64*", Size), Size &= 0xFFFFFFFF
				
				if (Size > MaxWidth)
					MaxWidth := Size
			}
			
			return MaxWidth
		}
		
		; Shows the suggestion dialog with contents of the provided DisplayList
		_Show(DisplayList)
		{
			; Insert the new list
			GuiControl,, % this.hListBox, %DisplayList%
			GuiControl, Choose, % this.hListBox, 1
			
			; Resize to fit contents
			StrReplace(DisplayList, "|",, Rows)
			Height := Rows * this.ListBoxItemHeight + this.ListBoxMargins
			Width := this._GetWidth(DisplayList) + 10
			GuiControl, Move, % this.hListBox, w%Width% h%Height%
			
			; Keep the dialog from running off the screen
			X := this.CaretX, Y := this.CaretY + 20
			if ((X + Width) > this.ScreenWidth)
				X := this.ScreenWidth - Width
			
			; Make the dialog visible
			Gui, % this.hWnd ":Show", x%X% y%Y% AutoSize NoActivate
			this.Visible := True
		}
		
		; Hides the dialog if it is visible
		_Hide()
		{
			if !this.Visible
				return
			
			Gui, % this.hWnd ":Hide"
			this.Visible := False
		}
		
		; Filters the word list for entries starting with the fragment, then
		; shows the dialog with the filtered list as suggestions
		_Suggest()
		{
			; Filter the list for words beginning with the fragment
			Suggestions := LTrim(RegExReplace(this.WordList
			, "i)\|(?!" this.Fragment ")[^\|]+"), "|")
			
			; Fail out if there were no matches
			if !Suggestions
				return true, this._Hide()
			
			; Pull the first MaxSuggestions suggestions
			if (Pos := InStr(Suggestions, "|",,, this.MaxSuggestions))
				Suggestions := SubStr(Suggestions, 1, Pos-1)
			this.Suggestions := Suggestions
			
			this._Show("|" Suggestions)
		}
		
		; Finishes the fragment with the selected suggestion
		_Complete()
		{
			; Get the text of the selected item
			GuiControlGet, Selected,, % this.hListBox
			Suggestion := StrSplit(this.Suggestions, "|")[Selected]
			
			; Replace fragment preceding cursor with selected suggestion
			RC := this.Parent.RichCode
			RC.Selection[1] -= StrLen(this.Fragment)
			RC.SelectedText := Suggestion
			RC.Selection[1] := RC.Selection[2]
			
			; Clear out the fragment in preparation for further typing
			this.Fragment := ""
		}
		
		
		; --- Public Methods ---
		
		; Interpret WM_KEYDOWN messages, the primary means of interfacing with the
		; class. These messages can be provided by registering an appropriate
		; handler with OnMessage, or by forwarding the events from another handler
		; for the control.
		WM_KEYDOWN(wParam, lParam)
		{
			if (!this._Enabled)
				return
			
			; Get the name of the key using the virtual key code. The key's scan
			; code is not used here, but is available in bits 16-23 of lParam and
			; could be used in future versions for greater reliability.
			Key := GetKeyName(Format("vk{:02x}", wParam))
			
			; Treat Numpad variants the same as the equivalent standard keys
			Key := StrReplace(Key, "Numpad")
			
			; Handle presses meant to interact with the dialog, such as
			; navigational, confirmational, or dismissive commands.
			if (this.Visible)
			{
				if (Key == "Tab" || Key == "Enter")
					return False, this._Complete()
				else if (Key == "Up")
					return False, this.SelectUp()
				else if (Key == "Down")
					return False, this.SelectDown()
			}
			
			; Ignore standalone modifier presses, and some modified regular presses
			if Key in Shift,Control,Alt
				return
			
			; Reset on presses with the control modifier
			if GetKeyState("Control")
				return "", this.Fragment := ""
			
			; Subtract from the end of fragment on backspace
			if (Key == "Backspace")
				return "", this.Fragment := SubStr(this.Fragment, 1, -1)
			
			; Apply Shift and CapsLock
			if GetKeyState("Shift")
				Key := StrReplace(Key, "-", "_")
			if (GetKeyState("Shift") ^ GetKeyState("CapsLock", "T"))
				Key := Format("{:U}", Key)
			
			; Reset on unwanted presses -- Allow numbers but not at beginning
			if !(Key ~= "^[A-Za-z_]$" || (this.Fragment != "" && Key ~= "^[0-9]$"))
				return "", this.Fragment := ""
			
			; Record the starting position of new fragments
			if (this.Fragment == "")
			{
				CoordMode, Caret, % this.WineVer ? "Client" : "Screen"
				
				; Round "" to 0, which can prevent errors in the unlikely case that
				; input is received while the control is not focused.
				this.CaretX := Round(A_CaretX), this.CaretY := Round(A_CaretY)
			}
			
			; Update fragment with the press
			this.Fragment .= Key
		}
		
		; Triggers a rebuild of the word list from the RichCode control's contents
		BuildWordList()
		{
			if (!this._Enabled)
				return
			
			; Replace non-word chunks with delimiters
			List := RegExReplace(this.Parent.RichCode.Value, "\W+", "|")
			
			; Ignore numbers at the beginning of words
			List := RegExReplace(List, "\b[0-9]+")
			
			; Ignore words that are too small
			List := RegExReplace(List, "\b\w{1," this.MinWordLen-1 "}\b")
			
			; Append default entries, remove duplicates, and save the list
			List .= this.DefaultWordList
			Sort, List, U D| Z
			this.WordList := "|" Trim(List, "|")
		}
		
		; Moves the selected item in the dialog up one position
		SelectUp()
		{
			GuiControlGet, Selected,, % this.hListBox
			if (--Selected < 1)
				Selected := this.MaxSuggestions
			GuiControl, Choose, % this.hListBox, %Selected%
		}
		
		; Moves the selected item in the dialog down one position
		SelectDown()
		{
			GuiControlGet, Selected,, % this.hListBox
			if (++Selected > this.MaxSuggestions)
				Selected := 1
			GuiControl, Choose, % this.hListBox, %Selected%
		}
	}
}
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 _ := WinEvents.AutoInit()
	
	AutoInit()
	{
		this.Table := []
		OnMessage(2, this.Destroy.bind(this))
	}
	
	Register(ID, HandlerClass, Prefix="Gui")
	{
		Gui, %ID%: +hWndhWnd +LabelWinEvents_
		this.Table[hWnd] := {Class: HandlerClass, Prefix: Prefix}
	}
	
	Unregister(ID)
	{
		Gui, %ID%: +hWndhWnd
		this.Table.Delete(hWnd)
	}
	
	Dispatch(Type, Params*)
	{
		Info := this.Table[Params[1]]
		return (Info.Class)[Info.Prefix . Type](Params*)
	}
	
	Destroy(wParam, lParam, Msg, hWnd)
	{
		this.Table.Delete(hWnd)
	}
}

WinEvents_Close(Params*) {
	return WinEvents.Dispatch("Close", Params*)
} WinEvents_Escape(Params*) {
	return WinEvents.Dispatch("Escape", Params*)
} WinEvents_Size(Params*) {
	return WinEvents.Dispatch("Size", Params*)
} WinEvents_ContextMenu(Params*) {
	return WinEvents.Dispatch("ContextMenu", Params*)
} WinEvents_DropFiles(Params*) {
	return WinEvents.Dispatch("DropFiles", Params*)
}
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="")
{
	static Shell := ComObjCreate("WScript.Shell")
	Name := "\\.\pipe\AHK_CQT_" A_TickCount
	Pipe := []
	Loop, 3
	{
		Pipe[A_Index] := DllCall("CreateNamedPipe"
		, "Str", 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)
	if (A_IsCompiled && AhkPath == A_ScriptFullPath)
		AhkPath .= " /E"
	if FileExist(Name)
	{
		Exec := Shell.Exec(AhkPath " /CP65001 " Name " " Params)
		DllCall("ConnectNamedPipe", "UPtr", Pipe[2], "UPtr", 0)
		DllCall("ConnectNamedPipe", "UPtr", Pipe[3], "UPtr", 0)
		FileOpen(Pipe[3], "h", "UTF-8").Write(Script)
	}
	else ; Running under WINE with improperly implemented pipes
	{
		FileOpen(Name := "AHK_CQT_TMP.ahk", "w").Write(Script)
		Exec := Shell.Exec(AhkPath " /CP65001 " Name " " Params)
	}
	Loop, 3
		DllCall("CloseHandle", "UPtr", Pipe[A_Index])
	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)
{
	xhr := ComObjCreate("MSXML2.XMLHTTP")
	xhr.Open("GET", url, false), xhr.Send()
	return xhr.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%
	return ErrorLevel
}

PostMessage(Msg, wParam, lParam, hWnd)
{
	PostMessage, Msg, wParam, lParam,, ahk_id %hWnd%
	return ErrorLevel
}

Ahkbin(Content, Name="", Desc="", Channel="")
{
	static URL := "https://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("MSXML2.XMLHTTP")
	Pbin.Open("POST", URL, False)
	Pbin.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
	Pbin.Send(Form)
	return Pbin.getResponseHeader("ahk-location")
}

; 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
}

CreateMenus(Menu)
{
	static MenuName := 0
	Menus := ["Menu_" MenuName++]
	for each, Item in Menu
	{
		Ref := Item[2]
		if IsObject(Ref) && Ref._NewEnum()
		{
			SubMenus := CreateMenus(Ref)
			Menus.Push(SubMenus*), Ref := ":" SubMenus[1]
		}
		Menu, % Menus[1], Add, % Item[1], %Ref%
	}
	return Menus
}

Ini_Load(Contents)
{
	Section := Out := []
	loop, Parse, Contents, `n, `r
	{
		if ((Line := Trim(A_LoopField)) ~= "^;|^$")
			continue
		else if RegExMatch(Line, "^\[(.+)\]$", Match)
			Out[Match1] := (Section := [])
		else if RegExMatch(Line, "^(.+?)=(.*)$", Match)
			Section[Trim(Match1)] := Trim(Match2)
	}
	return Out
}

GetFullPathName(FilePath)
{
	VarSetCapacity(Path, A_IsUnicode ? 520 : 260, 0)
	DllCall("GetFullPathName", "Str", FilePath
	, "UInt", 260, "Str", Path, "Ptr", 0, "UInt")
	return Path
}

RichEdit_AddMargins(hRichEdit, x:=0, y:=0, w:=0, h:=0)
{
	static WineVer := DllCall("ntdll.dll\wine_get_version", "AStr")
	VarSetCapacity(RECT, 16, 0)
	if (x | y | w | h)
	{
		if WineVer
		{
			; Workaround for bug in Wine 3.0.2.
			; This code will need to be updated this code
			; after future Wine releases that fix it.
			NumPut(x, RECT,  0, "Int"), NumPut(y, RECT,  4, "Int")
			NumPut(w, RECT,  8, "Int"), NumPut(h, RECT, 12, "Int")
		}
		else
		{
			if !DllCall("GetClientRect", "UPtr", hRichEdit, "UPtr", &RECT, "UInt")
				throw Exception("Couldn't get RichEdit Client RECT")
			NumPut(x + NumGet(RECT,  0, "Int"), RECT,  0, "Int")
			NumPut(y + NumGet(RECT,  4, "Int"), RECT,  4, "Int")
			NumPut(w + NumGet(RECT,  8, "Int"), RECT,  8, "Int")
			NumPut(h + NumGet(RECT, 12, "Int"), RECT, 12, "Int")
		}
	}
	SendMessage(0xB3, 0, &RECT, hRichEdit)
}

;
; Based on code from fincs' Ahk2Exe - https://github.com/fincs/ahk2exe
;

PreprocessScript(ByRef ScriptText, AhkScript, KeepComments=1, KeepIndent=1, KeepEmpties=0, FileList="", FirstScriptDir="", Options="", iOption=0)
{
	SplitPath, AhkScript, ScriptName, ScriptDir
	if !IsObject(FileList)
	{
		FileList := [AhkScript]
		; ScriptText := "; <COMPILER: v" A_AhkVersion ">`n"
		FirstScriptDir := A_WorkingDir
		IsFirstScript := true
		Options := { comm: ";", esc: "``" }
		
		OldWorkingDir := A_WorkingDir
		SetWorkingDir, %FirstScriptDir%
	}
	
	IfNotExist, %AhkScript%
		if !iOption
			Util_Error((IsFirstScript ? "Script" : "#include") " file """ AhkScript """ cannot be opened.")
	else return
		
	cmtBlock := false, contSection := false
	Loop, Read, %AhkScript%
	{
		tline := Trim(A_LoopReadLine)
		RegExMatch(A_LoopReadLine, "^[ \t]+", indent)
		if !cmtBlock
		{
			if !contSection
			{
				if StrStartsWith(tline, Options.comm) && !KeepComments
					continue
				else if (tline = "" && !KeepEmpties)
					continue
				else if StrStartsWith(tline, "/*")
				{
					if KeepComments
						ScriptText .= A_LoopReadLine "`n"
					cmtBlock := true
					continue
				}
			}
			if StrStartsWith(tline, "(") && !IsFakeCSOpening(tline)
				contSection := true
			else if StrStartsWith(tline, ")")
				contSection := false
			
			ttline := RegExReplace(tline, "\s+" RegExEscape(Options.comm) ".*$", "")
			if !contSection && RegExMatch(ttline, "i)^#Include(Again)?[ \t]*[, \t]?\s+(.*)$", o)
			{
				IsIncludeAgain := (o1 = "Again")
				IgnoreErrors := false
				IncludeFile := o2
				if RegExMatch(IncludeFile, "\*[iI]\s+?(.*)", o)
					IgnoreErrors := true, IncludeFile := Trim(o1)
				
				if RegExMatch(IncludeFile, "^<(.+)>$", o)
				{
					if IncFile2 := FindLibraryFile(o1, FirstScriptDir)
					{
						IncludeFile := IncFile2
						goto _skip_findfile
					}
				}
				
				StringReplace, IncludeFile, IncludeFile, `%A_ScriptDir`%, %FirstScriptDir%, All
				StringReplace, IncludeFile, IncludeFile, `%A_AppData`%, %A_AppData%, All
				StringReplace, IncludeFile, IncludeFile, `%A_AppDataCommon`%, %A_AppDataCommon%, All
				StringReplace, IncludeFile, IncludeFile, `%A_LineFile`%, %AhkScript%, All
				
				if InStr(FileExist(IncludeFile), "D")
				{
					SetWorkingDir, %IncludeFile%
					continue
				}
				
				_skip_findfile:
				
				IncludeFile := Util_GetFullPath(IncludeFile)
				
				AlreadyIncluded := false
				for k,v in FileList
					if (v = IncludeFile)
					{
						AlreadyIncluded := true
						break
					}
				if(IsIncludeAgain || !AlreadyIncluded)
				{
					if !AlreadyIncluded
						FileList.Insert(IncludeFile)
					PreprocessScript(ScriptText, IncludeFile, KeepComments, KeepIndent, KeepEmpties, FileList, FirstScriptDir, Options, IgnoreErrors)
				}
			}else if !contSection && ttline ~= "i)^FileInstall[, \t]"
			{
				if ttline ~= "^\w+\s+(:=|\+=|-=|\*=|/=|//=|\.=|\|=|&=|\^=|>>=|<<=)"
					continue ; This is an assignment!
				
				; workaround for `, detection
				EscapeChar := Options.esc
				EscapeCharChar := EscapeChar EscapeChar
				EscapeComma := EscapeChar ","
				EscapeTmp := chr(2)
				EscapeTmpD := chr(3)
				StringReplace, ttline, ttline, %EscapeCharChar%, %EscapeTmpD%, All
				StringReplace, ttline, ttline, %EscapeComma%, %EscapeTmp%, All
				
				if !RegExMatch(ttline, "i)^FileInstall[ \t]*[, \t][ \t]*([^,]+?)[ \t]*(,|$)", o) || o1 ~= "[^``]%"
					Util_Error("Error: Invalid ""FileInstall"" syntax found. Note that the first parameter must not be specified using a continuation section.")
				_ := Options.esc
				StringReplace, o1, o1, %_%`%, `%, All
				StringReplace, o1, o1, %_%`,, `,, All
				StringReplace, o1, o1, %_%%_%,, %_%,, All
				
				; workaround for `, detection [END]
				StringReplace, o1, o1, %EscapeTmp%, `,, All
				StringReplace, o1, o1, %EscapeTmpD%, %EscapeChar%, All
				StringReplace, ttline, ttline, %EscapeTmp%, %EscapeComma%, All
				StringReplace, ttline, ttline, %EscapeTmpD%, %EscapeCharChar%, All
				
				ScriptText .= (KeepIndent ? indent : "") (KeepComments ? tline : ttline) "`n"
			}else if !contSection && RegExMatch(tline, "i)^#CommentFlag\s+(.+)$", o)
				Options.comm := o1, ScriptText .= (KeepIndent ? indent : "") (KeepComments ? tline : ttline) "`n"
			else if !contSection && RegExMatch(tline, "i)^#EscapeChar\s+(.+)$", o)
				Options.esc := o1, ScriptText .= (KeepIndent ? indent : "") (KeepComments ? tline : ttline) "`n"
			else if !contSection && RegExMatch(tline, "i)^#DerefChar\s+(.+)$", o)
				Util_Error("Error: #DerefChar is not supported.")
			else if !contSection && RegExMatch(tline, "i)^#Delimiter\s+(.+)$", o)
				Util_Error("Error: #Delimiter is not supported.")
			else
				ScriptText .= (contSection ? A_LoopReadLine : (KeepIndent ? indent : "") (KeepComments ? tline : ttline)) "`n"
		}else{
			if KeepComments
				ScriptText .= A_LoopReadLine "`n"
			if StrStartsWith(tline, "*/")
				cmtBlock := false
		}
	}
	
	Loop, % !!IsFirstScript ; equivalent to "if IsFirstScript" except you can break from the block
	{
		static AhkPath := A_IsCompiled ? A_ScriptDir "\..\AutoHotkey.exe" : A_AhkPath
		IfNotExist, %AhkPath%
			break ; Don't bother with auto-includes because the file does not exist
		
		; Auto-including any functions called from a library...
		ilibfile = %A_Temp%\_ilib.ahk
		IfExist, %ilibfile%, FileDelete, %ilibfile%
			AhkType := AHKType(AhkPath)
		if AhkType = FAIL
			Util_Error("Error: The AutoHotkey build used for auto-inclusion of library functions is not recognized.", 1, AhkPath)
		if AhkType = Legacy
			Util_Error("Error: Legacy AutoHotkey versions (prior to v1.1) are not allowed as the build used for auto-inclusion of library functions.", 1, AhkPath)
		tmpErrorLog := Util_TempFile()
		RunWait, "%AhkPath%" /iLib "%ilibfile%" /ErrorStdOut "%AhkScript%" 2>"%tmpErrorLog%", %FirstScriptDir%, UseErrorLevel
		FileRead,tmpErrorData,%tmpErrorLog%
		FileDelete,%tmpErrorLog%
		if (ErrorLevel = 2)
			Util_Error("Error: The script contains syntax errors.",1,tmpErrorData)
		IfExist, %ilibfile%
		{
			PreprocessScript(ScriptText, ilibfile, KeepComments, KeepIndent, KeepEmpties, FileList, FirstScriptDir, Options)
			FileDelete, %ilibfile%
		}
		StringTrimRight, ScriptText, ScriptText, 1 ; remove trailing newline
	}
	
	if OldWorkingDir
		SetWorkingDir, %OldWorkingDir%
}

IsFakeCSOpening(tline)
{
	Loop, Parse, tline, %A_Space%%A_Tab%
		if !StrStartsWith(A_LoopField, "Join") && InStr(A_LoopField, ")")
			return true
	return false
}

FindLibraryFile(name, ScriptDir)
{
	libs := [ScriptDir "\Lib", A_MyDocuments "\AutoHotkey\Lib", A_ScriptDir "\..\Lib"]
	p := InStr(name, "_")
	if p
		name_lib := SubStr(name, 1, p-1)
	
	for each,lib in libs
	{
		file := lib "\" name ".ahk"
		IfExist, %file%
			return file
		
		if !p
			continue
		
		file := lib "\" name_lib ".ahk"
		IfExist, %file%
			return file
	}
}

StrStartsWith(ByRef v, ByRef w)
{
	return SubStr(v, 1, StrLen(w)) = w
}

RegExEscape(String)
{
	return "\Q" StrReplace(String, "\E", "\E\\E\Q") "\E"
}

Util_TempFile(d:="")
{
	if ( !StrLen(d) || !FileExist(d) )
		d:=A_Temp
	Loop
		tempName := d "\~temp" A_TickCount ".tmp"
	until !FileExist(tempName)
	return tempName
}

Util_GetFullPath(path)
{
	VarSetCapacity(fullpath, 260 * (!!A_IsUnicode + 1))
	if DllCall("GetFullPathName", "str", path, "uint", 260, "str", fullpath, "ptr", 0, "uint")
		return fullpath
	else
		return ""
}

Util_Error(txt, doexit=1, extra="")
{
	throw Exception(txt, -2, extra)
}

; Based on code from SciTEDebug.ahk
AHKType(exeName)
{
	FileGetVersion, vert, %exeName%
	if !vert
		return "FAIL"
	
	StringSplit, vert, vert, .
	vert := vert4 | (vert3 << 8) | (vert2 << 16) | (vert1 << 24)
	
	exeMachine := GetExeMachine(exeName)
	if !exeMachine
		return "FAIL"
	
	if (exeMachine != 0x014C) && (exeMachine != 0x8664)
		return "FAIL"
	
	if !(VersionInfoSize := DllCall("version\GetFileVersionInfoSize", "str", exeName, "uint*", null, "uint"))
		return "FAIL"
	
	VarSetCapacity(VersionInfo, VersionInfoSize)
	if !DllCall("version\GetFileVersionInfo", "str", exeName, "uint", 0, "uint", VersionInfoSize, "ptr", &VersionInfo)
		return "FAIL"
	
	if !DllCall("version\VerQueryValue", "ptr", &VersionInfo, "str", "\VarFileInfo\Translation", "ptr*", lpTranslate, "uint*", cbTranslate)
		return "FAIL"
	
	oldFmt := A_FormatInteger
	SetFormat, IntegerFast, H
	wLanguage := NumGet(lpTranslate+0, "UShort")
	wCodePage := NumGet(lpTranslate+2, "UShort")
	id := SubStr("0000" SubStr(wLanguage, 3), -3, 4) SubStr("0000" SubStr(wCodePage, 3), -3, 4)
	SetFormat, IntegerFast, %oldFmt%
	
	if !DllCall("version\VerQueryValue", "ptr", &VersionInfo, "str", "\StringFileInfo\" id "\ProductName", "ptr*", pField, "uint*", cbField)
		return "FAIL"
	
	; Check it is actually an AutoHotkey executable
	if !InStr(StrGet(pField, cbField), "AutoHotkey")
		return "FAIL"
	
	; We're dealing with a legacy version if it's prior to v1.1
	return vert >= 0x01010000 ? "Modern" : "Legacy"
}

GetExeMachine(exepath)
{
	if !(exe := FileOpen(exepath, "r"))
		return
	
	exe.Seek(60), exe.Seek(exe.ReadUInt()+4)
	return exe.ReadUShort()
}
class HelpFile
{
	static BaseURL := "ms-its:" A_AhkPath "\..\AutoHotkey.chm::/docs/"
	static Cache := {"Syntax": {}}
	
	GetPage(Path)
	{
		static xhttp := ComObjCreate("MSXML2.XMLHTTP.3.0")
		html := ComObjCreate("htmlfile")
		Path := this.BaseURL . RegExReplace(Path, "[?#].+")
		xhttp.open("GET", Path, True), xhttp.send()
		html.open(), html.write(xhttp.responseText), html.close()
		while !(html.readyState = "interactive" || html.readyState = "complete")
			Sleep, 50
		return html
	}
	
	GetLookup()
	{
		if this.Lookup
			return this.Lookup
		
		; Scrape the command reference
		this.Commands := {}
		try
			Page := this.GetPage("commands/index.htm")
		
		try ; Windows
			rows := Page.querySelectorAll(".info td:first-child a")
		catch ; Wine
			try
				rows := Page.body.querySelectorAll(".info td:first-child a")
		catch ; IE8
		{
			rows := new this.HTMLCollection()
			trows := Page.getElementsByTagName("table")[0].children[0].children
			loop, % trows.length
				rows.push(trows.Item(A_Index-1).children[0].children[0])
		}
		
		loop, % rows.length
			for i, text in StrSplit((row := rows.Item(A_Index-1)).innerText, "/")
				if RegExMatch(text, "^[\w#]+", Match) && !this.Commands.HasKey(Match)
					this.Commands[Match] := "commands/" RegExReplace(row.getAttribute("href"), "^about:")
		
		; Scrape the variables page
		this.Variables := {}
		try
			Page := this.GetPage("Variables.htm")
		
		try ; Windows
			rows := Page.querySelectorAll(".info td:first-child")
		catch ; Wine
			try
				rows := Page.body.querySelectorAll(".info td:first-child")
		catch ; IE8
		{
			rows := new this.HTMLCollection()
			tables := Page.getElementsByTagName("table")
			loop, % tables.length
			{
				trows := tables.Item(A_Index-1).children[0].children
				loop, % trows.length
					rows.push(trows.Item(A_Index-1).children[0])
			}
		}
		
		loop, % rows.length
			if RegExMatch((row := rows.Item(A_Index-1)).innerText, "(A_\w+)", Match)
				this.Variables[Match1] := "Variables.htm#" row.parentNode.getAttribute("id")
		
		; Combine
		this.Lookup := this.Commands.Clone()
		for k, v in this.Variables
			this.Lookup[k] := v
		
		return this.Lookup
	}
	
	Open(Keyword:="")
	{
		Lookup := this.GetLookup()
		Suffix := Lookup[Keyword] ? Lookup[Keyword] : "AutoHotkey.htm"
		Run, % "hh.exe """ this.BaseURL . Suffix """"
	}
	
	GetSyntax(Keyword:="")
	{
		; Generate this.Commands
		this.GetLookup()
		
		; Only look for Syntax of commands
		if !(Path := this.Commands[Keyword])
			return
		
		; Try to find it in the cache
		if this.Cache.Syntax.HasKey(Keyword)
			return this.Cache.Syntax[Keyword]
		
		; Get the right DOM to search
		Page := this.GetPage(Path)
		Root := Page ; Keep the page root in memory or it will be garbage collected
		if RegExMatch(Path, "#\K.+", ID)
			Page := Page.getElementById(ID)
		
		try ; Windows
			Nodes := page.getElementsByClassName("Syntax")
		catch ; Wine
			try
				Nodes := page.body.getElementsByClassName("Syntax")
		catch ; IE8
			Nodes := page.getElementsByTagName("pre")
		
		try ; Windows
			Text := Nodes.Item(0).innerText
		catch ; Some versions of Wine
			Text := Nodes.Item(0).innerHTML
		
		; Cache and return the result
		this.Cache.Syntax[Keyword] := StrSplit(Text, "`n", "`r")[1]
		return this.Cache.Syntax[Keyword]
	}
	
	class HTMLCollection
	{
		length[]
		{
			get
			{
				; Rounding MaxIndex produces a similar effect
				; to this.Length(), but doesn't trigger recursion
				return Round(this.MaxIndex())
			}
		}
		
		Item(i)
		{
			return this[i+1]
		}
	}
}
Angeliccare
Posts: 11
Joined: 08 Dec 2021, 17:33

Re: [Editor] CodeQuickTester - Write and run code without saving to a temporary file

13 Aug 2022, 18:27

If someone interested - script from the window of scr.:

Code: Select all

#singleinstance force
w:=600,h:=500,m:=50
Gui,+hWndn
Gui,Show,w%w% h%h%
Loop,% (w,d:=DllCall("GetDC",Ptr,n)){
Loop,% (h,j:=(x:=A_Index-1)*(3/w)-2.1){
k:=(y:=A_Index-1)*(2.5/h)-1.25,a:=b:=i:=0
while,(a*a+b*b<4&&i<m)
t:=a*a-b*b+j,b:=2*a*b+k,a:=t,i++
DllCall("SetPixel",Ptr,d,int,x,int,y,int,i=m?0:i/m*255)
}}MsgBox

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: gwarble and 111 guests