Advertisement
Najeebsk

CodeQuickTester.ahk

Jan 2nd, 2022
164
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #SingleInstance, Off
  2. #NoEnv
  3. SetBatchLines, -1
  4. SetWorkingDir, %A_ScriptDir%
  5.  
  6. global B_Params := []
  7. Loop, %0%
  8.     B_Params.Push(%A_Index%)
  9.  
  10. Menu, Tray, Icon, %A_AhkPath%, 2
  11. FileEncoding, UTF-8
  12.  
  13. Settings :=
  14. ( LTrim Join Comments
  15. {
  16.     ; File path for the starting contents
  17.     "DefaultPath": "C:\Windows\ShellNew\Template.ahk",
  18.  
  19.     ; When True, this setting may conflict with other instances of CQT
  20.     "GlobalRun": False,
  21.  
  22.     ; Script options
  23.     "AhkPath": A_AhkPath,
  24.     "Params": "",
  25.  
  26.     ; Editor (colors are 0xBBGGRR)
  27.     "FGColor": 0xEDEDCD,
  28.     "BGColor": 0x3F3F3F,
  29.     "TabSize": 4,
  30.     "Font": {
  31.         "Typeface": "Consolas",
  32.         "Size": 11,
  33.         "Bold": False
  34.     },
  35.     "Gutter": {
  36.         ; Width in pixels. Make this larger when using
  37.         ; larger fonts. Set to 0 to disable the gutter.
  38.         "Width": 40,
  39.  
  40.         "FGColor": 0x9FAFAF,
  41.         "BGColor": 0x262626
  42.     },
  43.  
  44.     ; Highlighter (colors are 0xRRGGBB)
  45.     "UseHighlighter": True,
  46.     "Highlighter": "HighlightAHK",
  47.     "HighlightDelay": 200, ; Delay until the user is finished typing
  48.     "Colors": {
  49.         "Comments":     0x7F9F7F,
  50.         "Functions":    0x7CC8CF,
  51.         "Keywords":     0xE4EDED,
  52.         "Multiline":    0x7F9F7F,
  53.         "Numbers":      0xF79B57,
  54.         "Punctuation":  0x97C0EB,
  55.         "Strings":      0xCC9893,
  56.         "A_Builtins":   0xF79B57,
  57.         "Commands":     0xCDBFA3,
  58.         "Directives":   0x7CC8CF,
  59.         "Flow":         0xE4EDED,
  60.         "KeyNames":     0xCB8DD9
  61.     },
  62.  
  63.     ; Auto-Indenter
  64.     "Indent": "`t",
  65.  
  66.     ; Pastebin
  67.     "DefaultName": A_UserName,
  68.     "DefaultDesc": "Pasted with CodeQuickTester",
  69.  
  70.     ; AutoComplete
  71.     "UseAutoComplete": True,
  72.     "ACListRebuildDelay": 500 ; Delay until the user is finished typing
  73. }
  74. )
  75.  
  76. ; Overlay any external settings onto the above defaults
  77. if FileExist("Settings.ini")
  78. {
  79.     ExtSettings := Ini_Load(FileOpen("Settings.ini", "r").Read())
  80.     for k, v in ExtSettings
  81.         if IsObject(v)
  82.             v.base := Settings[k]
  83.     ExtSettings.base := Settings
  84.     Settings := ExtSettings
  85. }
  86.  
  87. Tester := new CodeQuickTester(Settings)
  88. Tester.RegisterCloseCallback(Func("TesterClose"))
  89. return
  90.  
  91. #If Tester.Exec.Status == 0 ; Running
  92.  
  93. ~*Escape::Tester.Exec.Terminate()
  94.  
  95. #If (Tester.Settings.GlobalRun && Tester.Exec.Status == 0) ; Running
  96.  
  97. F5::
  98. !r::
  99. ; Reloads
  100. Tester.RunButton()
  101. Tester.RunButton()
  102. return
  103.  
  104. #If Tester.Settings.GlobalRun
  105.  
  106. F5::
  107. !r::
  108. Tester.RunButton()
  109. return
  110.  
  111. #If
  112.  
  113. TesterClose(Tester)
  114. {
  115.     ExitApp
  116. }
  117.  
  118. /*
  119.     class RichCode({"TabSize": 4     ; Width of a tab in characters
  120.     , "Indent": "`t"             ; What text to insert on indent
  121.     , "FGColor": 0xRRGGBB        ; Foreground (text) color
  122.     , "BGColor": 0xRRGGBB        ; Background color
  123.     , "Font"                     ; Font to use
  124.     : {"Typeface": "Courier New" ; Name of the typeface
  125.     , "Size": 12             ; Font size in points
  126.     , "Bold": False}         ; Bolded (True/False)
  127.    
  128.    
  129.     ; Whether to use the highlighter, or leave it as plain text
  130.     , "UseHighlighter": True
  131.    
  132.     ; Delay after typing before the highlighter is run
  133.     , "HighlightDelay": 200
  134.    
  135.     ; The highlighter function (FuncObj or name)
  136.     ; to generate the highlighted RTF. It will be passed
  137.     ; two parameters, the first being this settings array
  138.     ; and the second being the code to be highlighted
  139.     , "Highlighter": Func("HighlightAHK")
  140.    
  141.     ; The colors to be used by the highlighter function.
  142.     ; This is currently used only by the highlighter, not at all by the
  143.     ; RichCode class. As such, the RGB ordering is by convention only.
  144.     ; You can add as many colors to this array as you want.
  145.     , "Colors"
  146.     : [0xRRGGBB
  147.     , 0xRRGGBB
  148.     , 0xRRGGBB,
  149.     , 0xRRGGBB]})
  150. */
  151.  
  152. class RichCode
  153. {
  154.     static Msftedit := DllCall("LoadLibrary", "Str", "Msftedit.dll")
  155.     static IID_ITextDocument := "{8CC497C0-A1DF-11CE-8098-00AA0047BE5D}"
  156.     static MenuItems := ["Cut", "Copy", "Paste", "Delete", "", "Select All", ""
  157.     , "UPPERCASE", "lowercase", "TitleCase"]
  158.    
  159.     _Frozen := False
  160.    
  161.     ; --- Static Methods ---
  162.    
  163.     BGRFromRGB(RGB)
  164.     {
  165.         return RGB>>16&0xFF | RGB&0xFF00 | RGB<<16&0xFF0000
  166.     }
  167.    
  168.     ; --- Properties ---
  169.    
  170.     Value[]
  171.     {
  172.         get {
  173.             GuiControlGet, Code,, % this.hWnd
  174.             return Code
  175.         }
  176.        
  177.         set {
  178.             this.Highlight(Value)
  179.             return Value
  180.         }
  181.     }
  182.    
  183.     ; TODO: reserve and reuse memory
  184.     Selection[i:=0]
  185.     {
  186.         get {
  187.             VarSetCapacity(CHARRANGE, 8, 0)
  188.             this.SendMsg(0x434, 0, &CHARRANGE) ; EM_EXGETSEL
  189.             Out := [NumGet(CHARRANGE, 0, "Int"), NumGet(CHARRANGE, 4, "Int")]
  190.             return i ? Out[i] : Out
  191.         }
  192.        
  193.         set {
  194.             if i
  195.                 Temp := this.Selection, Temp[i] := Value, Value := Temp
  196.             VarSetCapacity(CHARRANGE, 8, 0)
  197.             NumPut(Value[1], &CHARRANGE, 0, "Int") ; cpMin
  198.             NumPut(Value[2], &CHARRANGE, 4, "Int") ; cpMax
  199.             this.SendMsg(0x437, 0, &CHARRANGE) ; EM_EXSETSEL
  200.             return Value
  201.         }
  202.     }
  203.    
  204.     SelectedText[]
  205.     {
  206.         get {
  207.             Selection := this.Selection, Length := Selection[2] - Selection[1]
  208.             VarSetCapacity(Buffer, (Length + 1) * 2) ; +1 for null terminator
  209.             if (this.SendMsg(0x43E, 0, &Buffer) > Length) ; EM_GETSELTEXT
  210.                 throw Exception("Text larger than selection! Buffer overflow!")
  211.             Text := StrGet(&Buffer, Selection[2]-Selection[1], "UTF-16")
  212.             return StrReplace(Text, "`r", "`n")
  213.         }
  214.        
  215.         set {
  216.             this.SendMsg(0xC2, 1, &Value) ; EM_REPLACESEL
  217.             this.Selection[1] -= StrLen(Value)
  218.             return Value
  219.         }
  220.     }
  221.    
  222.     EventMask[]
  223.     {
  224.         get {
  225.             return this._EventMask
  226.         }
  227.        
  228.         set {
  229.             this._EventMask := Value
  230.             this.SendMsg(0x445, 0, Value) ; EM_SETEVENTMASK
  231.             return Value
  232.         }
  233.     }
  234.    
  235.     UndoSuspended[]
  236.     {
  237.         get {
  238.             return this._UndoSuspended
  239.         }
  240.        
  241.         set {
  242.             try ; ITextDocument is not implemented in WINE
  243.             {
  244.                 if Value
  245.                     this.ITextDocument.Undo(-9999995) ; tomSuspend
  246.                 else
  247.                     this.ITextDocument.Undo(-9999994) ; tomResume
  248.             }
  249.             return this._UndoSuspended := !!Value
  250.         }
  251.     }
  252.    
  253.     Frozen[]
  254.     {
  255.         get {
  256.             return this._Frozen
  257.         }
  258.        
  259.         set {
  260.             if (Value && !this._Frozen)
  261.             {
  262.                 try ; ITextDocument is not implemented in WINE
  263.                     this.ITextDocument.Freeze()
  264.                 catch
  265.                     GuiControl, -Redraw, % this.hWnd
  266.             }
  267.             else if (!Value && this._Frozen)
  268.             {
  269.                 try ; ITextDocument is not implemented in WINE
  270.                     this.ITextDocument.Unfreeze()
  271.                 catch
  272.                     GuiControl, +Redraw, % this.hWnd
  273.             }
  274.             return this._Frozen := !!Value
  275.         }
  276.     }
  277.    
  278.     Modified[]
  279.     {
  280.         get {
  281.             return this.SendMsg(0xB8, 0, 0) ; EM_GETMODIFY
  282.         }
  283.        
  284.         set {
  285.             this.SendMsg(0xB9, Value, 0) ; EM_SETMODIFY
  286.             return Value
  287.         }
  288.     }
  289.    
  290.     ; --- Construction, Destruction, Meta-Functions ---
  291.    
  292.     __New(Settings, Options:="")
  293.     {
  294.         static Test
  295.         this.Settings := Settings
  296.         FGColor := this.BGRFromRGB(Settings.FGColor)
  297.         BGColor := this.BGRFromRGB(Settings.BGColor)
  298.        
  299.         Gui, Add, Custom, ClassRichEdit50W hWndhWnd +0x5031b1c4 +E0x20000 %Options%
  300.         this.hWnd := hWnd
  301.        
  302.         ; Enable WordWrap in RichEdit control ("WordWrap" : true)
  303.         if this.Settings.WordWrap
  304.             SendMessage, 0x0448, 0, 0, , % "ahk_id " . This.HWND
  305.        
  306.         ; Register for WM_COMMAND and WM_NOTIFY events
  307.         ; NOTE: this prevents garbage collection of
  308.         ; the class until the control is destroyed
  309.         this.EventMask := 1 ; ENM_CHANGE
  310.         CtrlEvent := this.CtrlEvent.Bind(this)
  311.         GuiControl, +g, %hWnd%, %CtrlEvent%
  312.        
  313.         ; Set background color
  314.         this.SendMsg(0x443, 0, BGColor) ; EM_SETBKGNDCOLOR
  315.        
  316.         ; Set character format
  317.         VarSetCapacity(CHARFORMAT2, 116, 0)
  318.         NumPut(116,                    CHARFORMAT2, 0,  "UInt")       ; cbSize      = sizeof(CHARFORMAT2)
  319.         NumPut(0xE0000000,             CHARFORMAT2, 4,  "UInt")       ; dwMask      = CFM_COLOR|CFM_FACE|CFM_SIZE
  320.         NumPut(FGColor,                CHARFORMAT2, 20, "UInt")       ; crTextColor = 0xBBGGRR
  321.         NumPut(Settings.Font.Size*20,  CHARFORMAT2, 12, "UInt")       ; yHeight     = twips
  322.         StrPut(Settings.Font.Typeface, &CHARFORMAT2+26, 32, "UTF-16") ; szFaceName  = TCHAR
  323.         this.SendMsg(0x444, 0, &CHARFORMAT2) ; EM_SETCHARFORMAT
  324.        
  325.         ; Set tab size to 4 for non-highlighted code
  326.         VarSetCapacity(TabStops, 4, 0), NumPut(Settings.TabSize*4, TabStops, "UInt")
  327.         this.SendMsg(0x0CB, 1, &TabStops) ; EM_SETTABSTOPS
  328.        
  329.         ; Change text limit from 32,767 to max
  330.         this.SendMsg(0x435, 0, -1) ; EM_EXLIMITTEXT
  331.        
  332.         ; Bind for keyboard events
  333.         ; Use a pointer to prevent reference loop
  334.         this.OnMessageBound := this.OnMessage.Bind(&this)
  335.         OnMessage(0x100, this.OnMessageBound) ; WM_KEYDOWN
  336.         OnMessage(0x205, this.OnMessageBound) ; WM_RBUTTONUP
  337.        
  338.         ; Bind the highlighter
  339.         this.HighlightBound := this.Highlight.Bind(&this)
  340.        
  341.         ; Create the right click menu
  342.         this.MenuName := this.__Class . &this
  343.         RCMBound := this.RightClickMenu.Bind(&this)
  344.         for Index, Entry in this.MenuItems
  345.             Menu, % this.MenuName, Add, %Entry%, %RCMBound%
  346.        
  347.         ; Get the ITextDocument object
  348.         VarSetCapacity(pIRichEditOle, A_PtrSize, 0)
  349.         this.SendMsg(0x43C, 0, &pIRichEditOle) ; EM_GETOLEINTERFACE
  350.         this.pIRichEditOle := NumGet(pIRichEditOle, 0, "UPtr")
  351.         this.IRichEditOle := ComObject(9, this.pIRichEditOle, 1), ObjAddRef(this.pIRichEditOle)
  352.         this.pITextDocument := ComObjQuery(this.IRichEditOle, this.IID_ITextDocument)
  353.         this.ITextDocument := ComObject(9, this.pITextDocument, 1), ObjAddRef(this.pITextDocument)
  354.     }
  355.    
  356.     RightClickMenu(ItemName, ItemPos, MenuName)
  357.     {
  358.         if !IsObject(this)
  359.             this := Object(this)
  360.        
  361.         if (ItemName == "Cut")
  362.             Clipboard := this.SelectedText, this.SelectedText := ""
  363.         else if (ItemName == "Copy")
  364.             Clipboard := this.SelectedText
  365.         else if (ItemName == "Paste")
  366.             this.SelectedText := Clipboard
  367.         else if (ItemName == "Delete")
  368.             this.SelectedText := ""
  369.         else if (ItemName == "Select All")
  370.             this.Selection := [0, -1]
  371.         else if (ItemName == "UPPERCASE")
  372.             this.SelectedText := Format("{:U}", this.SelectedText)
  373.         else if (ItemName == "lowercase")
  374.             this.SelectedText := Format("{:L}", this.SelectedText)
  375.         else if (ItemName == "TitleCase")
  376.             this.SelectedText := Format("{:T}", this.SelectedText)
  377.     }
  378.    
  379.     __Delete()
  380.     {
  381.         ; Release the ITextDocument object
  382.         this.ITextDocument := "", ObjRelease(this.pITextDocument)
  383.         this.IRichEditOle := "", ObjRelease(this.pIRichEditOle)
  384.        
  385.         ; Release the OnMessage handlers
  386.         OnMessage(0x100, this.OnMessageBound, 0) ; WM_KEYDOWN
  387.         OnMessage(0x205, this.OnMessageBound, 0) ; WM_RBUTTONUP
  388.        
  389.         ; Destroy the right click menu
  390.         Menu, % this.MenuName, Delete
  391.        
  392.         HighlightBound := this.HighlightBound
  393.         SetTimer, %HighlightBound%, Delete
  394.     }
  395.    
  396.     ; --- Event Handlers ---
  397.    
  398.     OnMessage(wParam, lParam, Msg, hWnd)
  399.     {
  400.         if !IsObject(this)
  401.             this := Object(this)
  402.         if (hWnd != this.hWnd)
  403.             return
  404.        
  405.         if (Msg == 0x100) ; WM_KEYDOWN
  406.         {
  407.             if (wParam == GetKeyVK("Tab"))
  408.             {
  409.                 ; Indentation
  410.                 Selection := this.Selection
  411.                 if GetKeyState("Shift")
  412.                     this.IndentSelection(True) ; Reverse
  413.                 else if (Selection[2] - Selection[1]) ; Something is selected
  414.                     this.IndentSelection()
  415.                 else
  416.                 {
  417.                     ; TODO: Trim to size needed to reach next TabSize
  418.                     this.SelectedText := this.Settings.Indent
  419.                     this.Selection[1] := this.Selection[2] ; Place cursor after
  420.                 }
  421.                 return False
  422.             }
  423.             else if (wParam == GetKeyVK("Escape")) ; Normally closes the window
  424.                 return False
  425.             else if (wParam == GetKeyVK("v") && GetKeyState("Ctrl"))
  426.             {
  427.                 this.SelectedText := Clipboard ; Strips formatting
  428.                 this.Selection[1] := this.Selection[2] ; Place cursor after
  429.                 return False
  430.             }
  431.         }
  432.         else if (Msg == 0x205) ; WM_RBUTTONUP
  433.         {
  434.             Menu, % this.MenuName, Show
  435.             return False
  436.         }
  437.     }
  438.    
  439.     CtrlEvent(CtrlHwnd, GuiEvent, EventInfo, _ErrorLevel:="")
  440.     {
  441.         if (GuiEvent == "Normal" && EventInfo == 0x300) ; EN_CHANGE
  442.         {
  443.             ; Delay until the user is finished changing the document
  444.             HighlightBound := this.HighlightBound
  445.             SetTimer, %HighlightBound%, % -Abs(this.Settings.HighlightDelay)
  446.         }
  447.     }
  448.    
  449.     ; --- Methods ---
  450.    
  451.     ; First parameter is taken as a replacement value
  452.     ; Variadic form is used to detect when a parameter is given,
  453.     ; regardless of content
  454.     Highlight(NewVal*)
  455.     {
  456.         if !IsObject(this)
  457.             this := Object(this)
  458.         if !(this.Settings.UseHighlighter && this.Settings.Highlighter)
  459.         {
  460.             if NewVal.Length()
  461.                 GuiControl,, % this.hWnd, % NewVal[1]
  462.             return
  463.         }
  464.        
  465.         ; Freeze the control while it is being modified, stop change event
  466.         ; generation, suspend the undo buffer, buffer any input events
  467.         PrevFrozen := this.Frozen, this.Frozen := True
  468.         PrevEventMask := this.EventMask, this.EventMask := 0 ; ENM_NONE
  469.         PrevUndoSuspended := this.UndoSuspended, this.UndoSuspended := True
  470.         PrevCritical := A_IsCritical
  471.         Critical, 1000
  472.        
  473.         ; Run the highlighter
  474.         Highlighter := this.Settings.Highlighter
  475.         RTF := %Highlighter%(this.Settings, NewVal.Length() ? NewVal[1] : this.Value)
  476.        
  477.         ; "TRichEdit suspend/resume undo function"
  478.         ; https://stackoverflow.com/a/21206620
  479.        
  480.         ; Save the rich text to a UTF-8 buffer
  481.         VarSetCapacity(Buf, StrPut(RTF, "UTF-8"), 0)
  482.         StrPut(RTF, &Buf, "UTF-8")
  483.        
  484.         ; Set up the necessary structs
  485.         VarSetCapacity(ZOOM,      8, 0) ; Zoom Level
  486.         VarSetCapacity(POINT,     8, 0) ; Scroll Pos
  487.         VarSetCapacity(CHARRANGE, 8, 0) ; Selection
  488.         VarSetCapacity(SETTEXTEX, 8, 0) ; SetText Settings
  489.         NumPut(1, SETTEXTEX, 0, "UInt") ; flags = ST_KEEPUNDO
  490.        
  491.         ; Save the scroll and cursor positions, update the text,
  492.         ; then restore the scroll and cursor positions
  493.         MODIFY := this.SendMsg(0xB8, 0, 0)    ; EM_GETMODIFY
  494.         this.SendMsg(0x4E0, &ZOOM, &ZOOM+4)   ; EM_GETZOOM
  495.         this.SendMsg(0x4DD, 0, &POINT)        ; EM_GETSCROLLPOS
  496.         this.SendMsg(0x434, 0, &CHARRANGE)    ; EM_EXGETSEL
  497.         this.SendMsg(0x461, &SETTEXTEX, &Buf) ; EM_SETTEXTEX
  498.         this.SendMsg(0x437, 0, &CHARRANGE)    ; EM_EXSETSEL
  499.         this.SendMsg(0x4DE, 0, &POINT)        ; EM_SETSCROLLPOS
  500.         this.SendMsg(0x4E1, NumGet(ZOOM, "UInt")
  501.         , NumGet(ZOOM, 4, "UInt"))        ; EM_SETZOOM
  502.         this.SendMsg(0xB9, MODIFY, 0)         ; EM_SETMODIFY
  503.        
  504.         ; Restore previous settings
  505.         Critical, %PrevCritical%
  506.         this.UndoSuspended := PrevUndoSuspended
  507.         this.EventMask := PrevEventMask
  508.         this.Frozen := PrevFrozen
  509.     }
  510.    
  511.     IndentSelection(Reverse:=False, Indent:="")
  512.     {
  513.         ; Freeze the control while it is being modified, stop change event
  514.         ; generation, buffer any input events
  515.         PrevFrozen := this.Frozen, this.Frozen := True
  516.         PrevEventMask := this.EventMask, this.EventMask := 0 ; ENM_NONE
  517.         PrevCritical := A_IsCritical
  518.         Critical, 1000
  519.        
  520.         if (Indent == "")
  521.             Indent := this.Settings.Indent
  522.         IndentLen := StrLen(Indent)
  523.        
  524.         ; Select back to the start of the first line
  525.         Min := this.Selection[1]
  526.         Top := this.SendMsg(0x436, 0, Min) ; EM_EXLINEFROMCHAR
  527.         TopLineIndex := this.SendMsg(0xBB, Top, 0) ; EM_LINEINDEX
  528.         this.Selection[1] := TopLineIndex
  529.        
  530.         ; TODO: Insert newlines using SetSel/ReplaceSel to avoid having to call
  531.         ; the highlighter again
  532.         Text := this.SelectedText
  533.         if Reverse
  534.         {
  535.             ; Remove indentation appropriately
  536.             Loop, Parse, Text, `n, `r
  537.             {
  538.                 if (InStr(A_LoopField, Indent) == 1)
  539.                 {
  540.                     Out .= "`n" SubStr(A_LoopField, 1+IndentLen)
  541.                     if (A_Index == 1)
  542.                         Min -= IndentLen
  543.                 }
  544.                 else
  545.                     Out .= "`n" A_LoopField
  546.             }
  547.             this.SelectedText := SubStr(Out, 2)
  548.            
  549.             ; Move the selection start back, but never onto the previous line
  550.             this.Selection[1] := Min < TopLineIndex ? TopLineIndex : Min
  551.         }
  552.         else
  553.         {
  554.             ; Add indentation appropriately
  555.             Trailing := (SubStr(Text, 0) == "`n")
  556.             Temp := Trailing ? SubStr(Text, 1, -1) : Text
  557.             Loop, Parse, Temp, `n, `r
  558.                 Out .= "`n" Indent . A_LoopField
  559.             this.SelectedText := SubStr(Out, 2) . (Trailing ? "`n" : "")
  560.            
  561.             ; Move the selection start forward
  562.             this.Selection[1] := Min + IndentLen
  563.         }
  564.        
  565.         this.Highlight()
  566.        
  567.         ; Restore previous settings
  568.         Critical, %PrevCritical%
  569.         this.EventMask := PrevEventMask
  570.        
  571.         ; When content changes cause the horizontal scrollbar to disappear,
  572.         ; unfreezing causes the scrollbar to jump. To solve this, jump back
  573.         ; after unfreezing. This will cause a flicker when that edge case
  574.         ; occurs, but it's better than the alternative.
  575.         VarSetCapacity(POINT, 8, 0)
  576.         this.SendMsg(0x4DD, 0, &POINT) ; EM_GETSCROLLPOS
  577.         this.Frozen := PrevFrozen
  578.         this.SendMsg(0x4DE, 0, &POINT) ; EM_SETSCROLLPOS
  579.     }
  580.    
  581.     ; --- Helper/Convenience Methods ---
  582.    
  583.     SendMsg(Msg, wParam, lParam)
  584.     {
  585.         SendMessage, Msg, wParam, lParam,, % "ahk_id" this.hWnd
  586.         return ErrorLevel
  587.     }
  588. }
  589. GenHighlighterCache(Settings)
  590. {
  591.     if Settings.HasKey("Cache")
  592.         return
  593.     Cache := Settings.Cache := {}
  594.    
  595.    
  596.     ; --- Process Colors ---
  597.     Cache.Colors := Settings.Colors.Clone()
  598.    
  599.     ; Inherit from the Settings array's base
  600.     BaseSettings := Settings
  601.     while (BaseSettings := BaseSettings.Base)
  602.         for Name, Color in BaseSettings.Colors
  603.             if !Cache.Colors.HasKey(Name)
  604.                 Cache.Colors[Name] := Color
  605.    
  606.     ; Include the color of plain text
  607.     if !Cache.Colors.HasKey("Plain")
  608.         Cache.Colors.Plain := Settings.FGColor
  609.    
  610.     ; Create a Name->Index map of the colors
  611.     Cache.ColorMap := {}
  612.     for Name, Color in Cache.Colors
  613.         Cache.ColorMap[Name] := A_Index
  614.    
  615.    
  616.     ; --- Generate the RTF headers ---
  617.     RTF := "{\urtf"
  618.    
  619.     ; Color Table
  620.     RTF .= "{\colortbl;"
  621.     for Name, Color in Cache.Colors
  622.     {
  623.         RTF .= "\red"   Color>>16 & 0xFF
  624.         RTF .= "\green" Color>>8  & 0xFF
  625.         RTF .= "\blue"  Color     & 0xFF ";"
  626.     }
  627.     RTF .= "}"
  628.    
  629.     ; Font Table
  630.     if Settings.Font
  631.     {
  632.         FontTable .= "{\fonttbl{\f0\fmodern\fcharset0 "
  633.         FontTable .= Settings.Font.Typeface
  634.         FontTable .= ";}}"
  635.         RTF .= "\fs" Settings.Font.Size * 2 ; Font size (half-points)
  636.         if Settings.Font.Bold
  637.             RTF .= "\b"
  638.     }
  639.    
  640.     ; Tab size (twips)
  641.     RTF .= "\deftab" GetCharWidthTwips(Settings.Font) * Settings.TabSize
  642.    
  643.     Cache.RTFHeader := RTF
  644. }
  645.  
  646. GetCharWidthTwips(Font)
  647. {
  648.     static Cache := {}
  649.    
  650.     if Cache.HasKey(Font.Typeface "_" Font.Size "_" Font.Bold)
  651.         return Cache[Font.Typeface "_" font.Size "_" Font.Bold]
  652.    
  653.     ; Calculate parameters of CreateFont
  654.     Height := -Round(Font.Size*A_ScreenDPI/72)
  655.     Weight := 400+300*(!!Font.Bold)
  656.     Face := Font.Typeface
  657.    
  658.     ; Get the width of "x"
  659.     hDC := DllCall("GetDC", "UPtr", 0)
  660.     hFont := DllCall("CreateFont"
  661.     , "Int", Height ; _In_ int     nHeight,
  662.     , "Int", 0      ; _In_ int     nWidth,
  663.     , "Int", 0      ; _In_ int     nEscapement,
  664.     , "Int", 0      ; _In_ int     nOrientation,
  665.     , "Int", Weight ; _In_ int     fnWeight,
  666.     , "UInt", 0     ; _In_ DWORD   fdwItalic,
  667.     , "UInt", 0     ; _In_ DWORD   fdwUnderline,
  668.     , "UInt", 0     ; _In_ DWORD   fdwStrikeOut,
  669.     , "UInt", 0     ; _In_ DWORD   fdwCharSet, (ANSI_CHARSET)
  670.     , "UInt", 0     ; _In_ DWORD   fdwOutputPrecision, (OUT_DEFAULT_PRECIS)
  671.     , "UInt", 0     ; _In_ DWORD   fdwClipPrecision, (CLIP_DEFAULT_PRECIS)
  672.     , "UInt", 0     ; _In_ DWORD   fdwQuality, (DEFAULT_QUALITY)
  673.     , "UInt", 0     ; _In_ DWORD   fdwPitchAndFamily, (FF_DONTCARE|DEFAULT_PITCH)
  674.     , "Str", Face   ; _In_ LPCTSTR lpszFace
  675.     , "UPtr")
  676.     hObj := DllCall("SelectObject", "UPtr", hDC, "UPtr", hFont, "UPtr")
  677.     VarSetCapacity(SIZE, 8, 0)
  678.     DllCall("GetTextExtentPoint32", "UPtr", hDC, "Str", "x", "Int", 1, "UPtr", &SIZE)
  679.     DllCall("SelectObject", "UPtr", hDC, "UPtr", hObj, "UPtr")
  680.     DllCall("DeleteObject", "UPtr", hFont)
  681.     DllCall("ReleaseDC", "UPtr", 0, "UPtr", hDC)
  682.    
  683.     ; Convert to twpis
  684.     Twips := Round(NumGet(SIZE, 0, "UInt")*1440/A_ScreenDPI)
  685.     Cache[Font.Typeface "_" Font.Size "_" Font.Bold] := Twips
  686.     return Twips
  687. }
  688.  
  689. EscapeRTF(Code)
  690. {
  691.     for each, Char in ["\", "{", "}", "`n"]
  692.         Code := StrReplace(Code, Char, "\" Char)
  693.     return StrReplace(StrReplace(Code, "`t", "\tab "), "`r")
  694. }
  695.  
  696. HighlightAHK(Settings, ByRef Code)
  697. {
  698.     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"
  699.     , 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"
  700.     , 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"
  701.     , 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"
  702.     , Builtins := "base|clipboard|clipboardall|comspec|errorlevel|false|programfiles|true"
  703.     , 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"
  704.     , Needle := "
  705.     ( LTrim Join Comments
  706.         ODims)
  707.         ((?:^|\s);[^\n]+)          ; Comments
  708.         |(^\s*\/\*.+?\n\s*\*\/)    ; Multiline comments
  709.         |((?:^|\s)#[^ \t\r\n,]+)   ; Directives
  710.         |([+*!~&\/\\<>^|=?:
  711.             ,().```%{}\[\]\-]+)    ; Punctuation
  712.         |\b(0x[0-9a-fA-F]+|[0-9]+) ; Numbers
  713.         |(""[^""\r\n]*"")          ; Strings
  714.         |\b(A_\w*|" Builtins ")\b  ; A_Builtins
  715.         |\b(" Flow ")\b            ; Flow
  716.         |\b(" Commands ")\b        ; Commands
  717.         |\b(" Functions ")\b       ; Functions (builtin)
  718.         |\b(" Keynames ")\b        ; Keynames
  719.         |\b(" Keywords ")\b        ; Other keywords
  720.         |(([a-zA-Z_$]+)(?=\())     ; Functions
  721.     )"
  722.    
  723.     GenHighlighterCache(Settings)
  724.     Map := Settings.Cache.ColorMap
  725.    
  726.     Pos := 1
  727.     while (FoundPos := RegExMatch(Code, Needle, Match, Pos))
  728.     {
  729.         RTF .= "\cf" Map.Plain " "
  730.         RTF .= EscapeRTF(SubStr(Code, Pos, FoundPos-Pos))
  731.        
  732.         ; Flat block of if statements for performance
  733.         if (Match.Value(1) != "")
  734.             RTF .= "\cf" Map.Comments
  735.         else if (Match.Value(2) != "")
  736.             RTF .= "\cf" Map.Multiline
  737.         else if (Match.Value(3) != "")
  738.             RTF .= "\cf" Map.Directives
  739.         else if (Match.Value(4) != "")
  740.             RTF .= "\cf" Map.Punctuation
  741.         else if (Match.Value(5) != "")
  742.             RTF .= "\cf" Map.Numbers
  743.         else if (Match.Value(6) != "")
  744.             RTF .= "\cf" Map.Strings
  745.         else if (Match.Value(7) != "")
  746.             RTF .= "\cf" Map.A_Builtins
  747.         else if (Match.Value(8) != "")
  748.             RTF .= "\cf" Map.Flow
  749.         else if (Match.Value(9) != "")
  750.             RTF .= "\cf" Map.Commands
  751.         else if (Match.Value(10) != "")
  752.             RTF .= "\cf" Map.Functions
  753.         else if (Match.Value(11) != "")
  754.             RTF .= "\cf" Map.Keynames
  755.         else if (Match.Value(12) != "")
  756.             RTF .= "\cf" Map.Keywords
  757.         else if (Match.Value(13) != "")
  758.             RTF .= "\cf" Map.Functions
  759.         else
  760.             RTF .= "\cf" Map.Plain
  761.        
  762.         RTF .= " " EscapeRTF(Match.Value())
  763.         Pos := FoundPos + Match.Len()
  764.     }
  765.    
  766.     return Settings.Cache.RTFHeader . RTF
  767.     . "\cf" Map.Plain " " EscapeRTF(SubStr(Code, Pos)) "\`n}"
  768. }
  769. class CodeQuickTester
  770. {
  771.     static Msftedit := DllCall("LoadLibrary", "Str", "Msftedit.dll")
  772.     EditorString := """" A_AhkPath """ """ A_ScriptFullPath """ ""%1"""
  773.     OrigEditorString := "notepad.exe %1"
  774.     Title := "NajeebCodeQuickTester"
  775.    
  776.     __New(Settings)
  777.     {
  778.         this.Settings := Settings
  779.        
  780.         this.Shell := ComObjCreate("WScript.Shell")
  781.        
  782.         this.Bound := []
  783.         this.Bound.RunButton := this.RunButton.Bind(this)
  784.         this.Bound.GuiSize := this.GuiSize.Bind(this)
  785.         this.Bound.OnMessage := this.OnMessage.Bind(this)
  786.         this.Bound.UpdateStatusBar := this.UpdateStatusBar.Bind(this)
  787.         this.Bound.UpdateAutoComplete := this.UpdateAutoComplete.Bind(this)
  788.         this.Bound.CheckIfRunning := this.CheckIfRunning.Bind(this)
  789.         this.Bound.Highlight := this.Highlight.Bind(this)
  790.         this.Bound.SyncGutter := this.SyncGutter.Bind(this)
  791.        
  792.         Buttons := new this.MenuButtons(this)
  793.         this.Bound.Indent := Buttons.Indent.Bind(Buttons)
  794.         this.Bound.Unindent := Buttons.Unindent.Bind(Buttons)
  795.         Menus :=
  796.         ( LTrim Join Comments
  797.         [
  798.             ["&File", [
  799.                 ["&Run`tF5", this.Bound.RunButton],
  800.                 [],
  801.                 ["&New`tCtrl+N", Buttons.New.Bind(Buttons)],
  802.                 ["&Open`tCtrl+O", Buttons.Open.Bind(Buttons)],
  803.                 ["Open &Working Dir`tCtrl+Shift+O", Buttons.OpenFolder.Bind(Buttons)],
  804.                 ["&Save`tCtrl+S", Buttons.Save.Bind(Buttons, False)],
  805.                 ["&Save as`tCtrl+Shift+S", Buttons.Save.Bind(Buttons, True)],
  806.                 ["Rename", Buttons.Rename.Bind(Buttons)],
  807.                 [],
  808.                 ["&Publish", Buttons.Publish.Bind(Buttons)],
  809.                 ["&Fetch", Buttons.Fetch.Bind(Buttons)],
  810.                 [],
  811.                 ["E&xit`tCtrl+W", this.GuiClose.Bind(this)]
  812.             ]], ["&Edit", [
  813.                 ["Find`tCtrl+F", Buttons.Find.Bind(Buttons)],
  814.                 [],
  815.                 ["Comment Lines`tCtrl+K", Buttons.Comment.Bind(Buttons)],
  816.                 ["Uncomment Lines`tCtrl+Shift+K", Buttons.Uncomment.Bind(Buttons)],
  817.                 [],
  818.                 ["Indent Lines", this.Bound.Indent],
  819.                 ["Unindent Lines", this.Bound.Unindent],
  820.                 [],
  821.                 ["Include &Relative", Buttons.IncludeRel.Bind(Buttons)],
  822.                 ["Include &Absolute", Buttons.IncludeAbs.Bind(Buttons)],
  823.                 [],
  824.                 ["Script &Options", Buttons.ScriptOpts.Bind(Buttons)]
  825.             ]], ["&Tools", [
  826.                 ["&Pastebin`tCtrl+P", Buttons.Paste.Bind(Buttons)],
  827.                 ["Re&indent`tCtrl+I", Buttons.AutoIndent.Bind(Buttons)],
  828.                 [],
  829.                 ["&AlwaysOnTop`tAlt+A", Buttons.ToggleOnTop.Bind(Buttons)],
  830.                 ["Global Run Hotkeys", Buttons.GlobalRun.Bind(Buttons)],
  831.                 [],
  832.                 ["Install Service Handler", Buttons.ServiceHandler.Bind(Buttons)],
  833.                 ["Set as Default Editor", Buttons.DefaultEditor.Bind(Buttons)],
  834.                 [],
  835.                 ["&Highlighter", Buttons.Highlighter.Bind(Buttons)],
  836.                 ["AutoComplete", Buttons.AutoComplete.Bind(Buttons)]
  837.             ]], ["&Help", [
  838.                 ["Open &Help File`tCtrl+H", Buttons.Help.Bind(Buttons)],
  839.                 ["&About", Buttons.About.Bind(Buttons)]
  840.             ]]
  841.         ]
  842.         )
  843.        
  844.         Gui, New, +Resize +hWndhMainWindow -AlwaysOnTop
  845.         this.AlwaysOnTop := False
  846.         this.hMainWindow := hMainWindow
  847.         this.Menus := CreateMenus(Menus)
  848.         Gui, Menu, % this.Menus[1]
  849.        
  850.         ; If set as default, check the highlighter option
  851.         if this.Settings.UseHighlighter
  852.             Menu, % this.Menus[4], Check, &Highlighter
  853.        
  854.         ; If set as default, check the global run hotkeys option
  855.         if this.Settings.GlobalRun
  856.             Menu, % this.Menus[4], Check, Global Run Hotkeys
  857.        
  858.         ; If set as default, check the AutoComplete option
  859.         if this.Settings.UseAutoComplete
  860.             Menu, % this.Menus[4], Check, AutoComplete
  861.        
  862.         ; If service handler is installed, check the menu option
  863.         if ServiceHandler.Installed()
  864.             Menu, % this.Menus[4], Check, Install Service Handler
  865.        
  866.         RegRead, Editor, HKCR, AutoHotkeyScript\Shell\Edit\Command
  867.         if (Editor == this.EditorString)
  868.             Menu, % this.Menus[4], Check, Set as Default Editor
  869.        
  870.         ; Register for events
  871.         WinEvents.Register(this.hMainWindow, this)
  872.         for each, Msg in [0x111, 0x100, 0x101, 0x201, 0x202, 0x204] ; WM_COMMAND, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN
  873.             OnMessage(Msg, this.Bound.OnMessage)
  874.        
  875.         ; Add code editor and gutter for line numbers
  876.         this.RichCode := new RichCode(this.Settings, "-E0x20000")
  877.         RichEdit_AddMargins(this.RichCode.hWnd, 3, 3)
  878.         if Settings.Gutter.Width
  879.             this.AddGutter()
  880.        
  881.         if B_Params.HasKey(1)
  882.             FilePath := RegExReplace(B_Params[1], "^ahk:") ; Remove leading service handler
  883.         else
  884.             FilePath := Settings.DefaultPath
  885.        
  886.         if (FilePath ~= "^https?://")
  887.             this.RichCode.Value := UrlDownloadToVar(FilePath)
  888.         else if (FilePath = "Clipboard")
  889.             this.RichCode.Value := Clipboard
  890.         else if InStr(FileExist(FilePath), "A")
  891.         {
  892.             this.RichCode.Value := FileOpen(FilePath, "r").Read()
  893.             this.RichCode.Modified := False
  894.            
  895.             if (FilePath == Settings.DefaultPath)
  896.             {
  897.                 ; Place cursor after the default template text
  898.                 this.RichCode.Selection := [-1, -1]
  899.             }
  900.             else
  901.             {
  902.                 ; Keep track of the file currently being edited
  903.                 this.FilePath := GetFullPathName(FilePath)
  904.                
  905.                 ; Follow the directory of the most recently opened file
  906.                 SetWorkingDir, %FilePath%\..
  907.             }
  908.         }
  909.         else
  910.             this.RichCode.Value := ""
  911.        
  912.         if (this.FilePath == "")
  913.             Menu, % this.Menus[2], Disable, Rename
  914.        
  915.         ; Add run button
  916.         Gui, Add, Button, hWndhRunButton, &Run
  917.         this.hRunButton := hRunButton
  918.         BoundFunc := this.Bound.RunButton
  919.         GuiControl, +g, %hRunButton%, %BoundFunc%
  920.        
  921.         ; Add status bar
  922.         Gui, Add, StatusBar, hWndhStatusBar
  923.         this.UpdateStatusBar()
  924.         ControlGetPos,,,, StatusBarHeight,, ahk_id %hStatusBar%
  925.         this.StatusBarHeight := StatusBarHeight
  926.        
  927.         ; Initialize the AutoComplete
  928.         this.AC := new this.AutoComplete(this, this.settings.UseAutoComplete)
  929.        
  930.         this.UpdateTitle()
  931.         Gui, Show, w640 h480
  932.     }
  933.    
  934.     AddGutter()
  935.     {
  936.         s := this.Settings, f := s.Font, g := s.Gutter
  937.        
  938.         ; Add the RichEdit control for the gutter
  939.         Gui, Add, Custom, ClassRichEdit50W hWndhGutter +0x5031b1c6 -HScroll -VScroll
  940.         this.hGutter := hGutter
  941.        
  942.         ; Set the background and font settings
  943.         FGColor := RichCode.BGRFromRGB(g.FGColor)
  944.         BGColor := RichCode.BGRFromRGB(g.BGColor)
  945.         VarSetCapacity(CF2, 116, 0)
  946.         NumPut(116,        &CF2+ 0, "UInt") ; cbSize      = sizeof(CF2)
  947.         NumPut(0xE<<28,    &CF2+ 4, "UInt") ; dwMask      = CFM_COLOR|CFM_FACE|CFM_SIZE
  948.         NumPut(f.Size*20,  &CF2+12, "UInt") ; yHeight     = twips
  949.         NumPut(FGColor,    &CF2+20, "UInt") ; crTextColor = 0xBBGGRR
  950.         StrPut(f.Typeface, &CF2+26, 32, "UTF-16") ; szFaceName = TCHAR
  951.         SendMessage(0x444, 0, &CF2,    hGutter) ; EM_SETCHARFORMAT
  952.         SendMessage(0x443, 0, BGColor, hGutter) ; EM_SETBKGNDCOLOR
  953.        
  954.         RichEdit_AddMargins(hGutter, 3, 3, -3, 0)
  955.     }
  956.    
  957.     RunButton()
  958.     {
  959.         if (this.Exec.Status == 0) ; Running
  960.             this.Exec.Terminate() ; CheckIfRunning updates the GUI
  961.         else ; Not running or doesn't exist
  962.         {
  963.             this.Exec := ExecScript(this.RichCode.Value
  964.             , this.Settings.Params
  965.             , this.Settings.AhkPath)
  966.            
  967.             GuiControl,, % this.hRunButton, &Kill
  968.            
  969.             SetTimer(this.Bound.CheckIfRunning, 100)
  970.         }
  971.     }
  972.    
  973.     CheckIfRunning()
  974.     {
  975.         if (this.Exec.Status == 1)
  976.         {
  977.             SetTimer(this.Bound.CheckIfRunning, "Delete")
  978.             GuiControl,, % this.hRunButton, &Run
  979.         }
  980.     }
  981.    
  982.     LoadCode(Code, FilePath:="")
  983.     {
  984.         ; Do nothing if nothing is changing
  985.         if (this.FilePath == FilePath && this.RichCode.Value == Code)
  986.             return
  987.        
  988.         ; Confirm the user really wants to load new code
  989.         Gui, +OwnDialogs
  990.         MsgBox, 308, % this.Title " - Confirm Overwrite"
  991.         , Are you sure you want to overwrite your code?
  992.         IfMsgBox, No
  993.             return
  994.        
  995.         ; If we're changing the open file mark as modified
  996.         ; If we're loading a new file mark as unmodified
  997.         this.RichCode.Modified := this.FilePath == FilePath
  998.         this.FilePath := FilePath
  999.         if (this.FilePath == "")
  1000.             Menu, % this.Menus[2], Disable, Rename
  1001.         else
  1002.             Menu, % this.Menus[2], Enable, Rename
  1003.        
  1004.         ; Update the GUI
  1005.         this.RichCode.Value := Code
  1006.         this.UpdateStatusBar()
  1007.     }
  1008.    
  1009.     OnMessage(wParam, lParam, Msg, hWnd)
  1010.     {
  1011.         if (hWnd == this.hMainWindow && Msg == 0x111 ; WM_COMMAND
  1012.             && lParam == this.RichCode.hWnd)         ; for RichEdit
  1013.         {
  1014.             Command := wParam >> 16
  1015.            
  1016.             if (Command == 0x400) ; An event that fires on scroll
  1017.             {
  1018.                 this.SyncGutter()
  1019.                
  1020.                 ; If the user is scrolling too fast it can cause some messages
  1021.                 ; to be dropped. Set a timer to make sure that when the user stops
  1022.                 ; scrolling that the line numbers will be in sync.
  1023.                 SetTimer(this.Bound.SyncGutter, -50)
  1024.             }
  1025.             else if (Command == 0x200) ; EN_KILLFOCUS
  1026.                 if this.Settings.UseAutoComplete
  1027.                     this.AC.Fragment := ""
  1028.         }
  1029.         else if (hWnd == this.RichCode.hWnd)
  1030.         {
  1031.             ; Call UpdateStatusBar after the edit handles the keystroke
  1032.             SetTimer(this.Bound.UpdateStatusBar, -0)
  1033.            
  1034.             if this.Settings.UseAutoComplete
  1035.             {
  1036.                 SetTimer(this.Bound.UpdateAutoComplete
  1037.                 , -Abs(this.Settings.ACListRebuildDelay))
  1038.                
  1039.                 if (Msg == 0x100) ; WM_KEYDOWN
  1040.                     return this.AC.WM_KEYDOWN(wParam, lParam)
  1041.                 else if (Msg == 0x201) ; WM_LBUTTONDOWN
  1042.                     this.AC.Fragment := ""
  1043.             }
  1044.         }
  1045.         else if (hWnd == this.hGutter
  1046.             && {0x100:1,0x101:1,0x201:1,0x202:1,0x204:1}[Msg]) ; WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN
  1047.         {
  1048.             ; Disallow interaction with the gutter
  1049.             return True
  1050.         }
  1051.     }
  1052.    
  1053.     SyncGutter()
  1054.     {
  1055.         static BUFF, _ := VarSetCapacity(BUFF, 16, 0)
  1056.        
  1057.         if !this.Settings.Gutter.Width
  1058.             return
  1059.        
  1060.         SendMessage(0x4E0, &BUFF, &BUFF+4, this.RichCode.hwnd) ; EM_GETZOOM
  1061.         SendMessage(0x4DD, 0, &BUFF+8, this.RichCode.hwnd)     ; EM_GETSCROLLPOS
  1062.        
  1063.         ; Don't update the gutter unnecessarily
  1064.         State := NumGet(BUFF, 0, "UInt") . NumGet(BUFF, 4, "UInt")
  1065.         . NumGet(BUFF, 8, "UInt") . NumGet(BUFF, 12, "UInt")
  1066.         if (State == this.GutterState)
  1067.             return
  1068.        
  1069.         NumPut(-1, BUFF, 8, "UInt") ; Don't sync horizontal position
  1070.         Zoom := [NumGet(BUFF, "UInt"), NumGet(BUFF, 4, "UInt")]
  1071.         PostMessage(0x4E1, Zoom[1], Zoom[2], this.hGutter)     ; EM_SETZOOM
  1072.         PostMessage(0x4DE, 0, &BUFF+8, this.hGutter)           ; EM_SETSCROLLPOS
  1073.         this.ZoomLevel := Zoom[1] / Zoom[2]
  1074.         if (this.ZoomLevel != this.LastZoomLevel)
  1075.             SetTimer(this.Bound.GuiSize, -0), this.LastZoomLevel := this.ZoomLevel
  1076.        
  1077.         this.GutterState := State
  1078.     }
  1079.    
  1080.     GetKeywordFromCaret()
  1081.     {
  1082.         ; https://autohotkey.com/boards/viewtopic.php?p=180369#p180369
  1083.         static Buffer
  1084.         IsUnicode := !!A_IsUnicode
  1085.        
  1086.         rc := this.RichCode
  1087.         sel := rc.Selection
  1088.        
  1089.         ; Get the currently selected line
  1090.         LineNum := rc.SendMsg(0x436, 0, sel[1]) ; EM_EXLINEFROMCHAR
  1091.        
  1092.         ; Size a buffer according to the line's length
  1093.         Length := rc.SendMsg(0xC1, sel[1], 0) ; EM_LINELENGTH
  1094.         VarSetCapacity(Buffer, Length << !!A_IsUnicode, 0)
  1095.         NumPut(Length, Buffer, "UShort")
  1096.        
  1097.         ; Get the text from the line
  1098.         rc.SendMsg(0xC4, LineNum, &Buffer) ; EM_GETLINE
  1099.         lineText := StrGet(&Buffer, Length)
  1100.        
  1101.         ; Parse the line to find the word
  1102.         LineIndex := rc.SendMsg(0xBB, LineNum, 0) ; EM_LINEINDEX
  1103.         RegExMatch(SubStr(lineText, 1, sel[1]-LineIndex), "[#\w]+$", Start)
  1104.         RegExMatch(SubStr(lineText, sel[1]-LineIndex+1), "^[#\w]+", End)
  1105.        
  1106.         return Start . End
  1107.     }
  1108.    
  1109.     UpdateStatusBar()
  1110.     {
  1111.         ; Delete the timer if it was called by one
  1112.         SetTimer(this.Bound.UpdateStatusBar, "Delete")
  1113.        
  1114.         ; Get the document length and cursor position
  1115.         VarSetCapacity(GTL, 8, 0), NumPut(1200, GTL, 4, "UInt")
  1116.         Len := this.RichCode.SendMsg(0x45F, &GTL, 0) ; EM_GETTEXTLENGTHEX (Handles newlines better than GuiControlGet on RE)
  1117.         ControlGet, Row, CurrentLine,,, % "ahk_id" this.RichCode.hWnd
  1118.         ControlGet, Col, CurrentCol,,, % "ahk_id" this.RichCode.hWnd
  1119.        
  1120.         ; Get Selected Text Length
  1121.         ; If the user has selected 1 char further than the end of the document,
  1122.         ; which is allowed in a RichEdit control, subtract 1 from the length
  1123.         Sel := this.RichCode.Selection
  1124.         Sel := Sel[2] - Sel[1] - (Sel[2] > Len)
  1125.        
  1126.         ; Get the syntax tip, if any
  1127.         if (SyntaxTip := HelpFile.GetSyntax(this.GetKeywordFromCaret()))
  1128.             this.SyntaxTip := SyntaxTip
  1129.        
  1130.         ; Update the Status Bar text
  1131.         Gui, % this.hMainWindow ":Default"
  1132.         SB_SetText("Len " Len ", Line " Row ", Col " Col
  1133.         . (Sel > 0 ? ", Sel " Sel : "") "     " this.SyntaxTip)
  1134.        
  1135.         ; Update the title Bar
  1136.         this.UpdateTitle()
  1137.        
  1138.         ; Update the gutter to match the document
  1139.         if this.Settings.Gutter.Width
  1140.         {
  1141.             ControlGet, Lines, LineCount,,, % "ahk_id" this.RichCode.hWnd
  1142.             if (Lines != this.LineCount)
  1143.             {
  1144.                 Loop, %Lines%
  1145.                     Text .= A_Index "`n"
  1146.                 GuiControl,, % this.hGutter, %Text%
  1147.                 this.SyncGutter()
  1148.                 this.LineCount := Lines
  1149.             }
  1150.         }
  1151.     }
  1152.    
  1153.     UpdateTitle()
  1154.     {
  1155.         Title := this.Title
  1156.        
  1157.         ; Show the current file name
  1158.         if this.FilePath
  1159.         {
  1160.             SplitPath, % this.FilePath, FileName
  1161.             Title .= " - " FileName
  1162.         }
  1163.        
  1164.         ; Show the curernt modification status
  1165.         if this.RichCode.Modified
  1166.             Title .= "*"
  1167.        
  1168.         ; Return if the title doesn't need to be updated
  1169.         if (Title == this.VisibleTitle)
  1170.             return
  1171.         this.VisibleTitle := Title
  1172.        
  1173.         HiddenWindows := A_DetectHiddenWindows
  1174.         DetectHiddenWindows, On
  1175.         WinSetTitle, % "ahk_id" this.hMainWindow,, %Title%
  1176.         DetectHiddenWindows, %HiddenWindows%
  1177.     }
  1178.    
  1179.     UpdateAutoComplete()
  1180.     {
  1181.         ; Delete the timer if it was called by one
  1182.         SetTimer(this.Bound.UpdateAutoComplete, "Delete")
  1183.        
  1184.         this.AC.BuildWordList()
  1185.     }
  1186.    
  1187.     RegisterCloseCallback(CloseCallback)
  1188.     {
  1189.         this.CloseCallback := CloseCallback
  1190.     }
  1191.    
  1192.     GuiSize()
  1193.     {
  1194.         static RECT, _ := VarSetCapacity(RECT, 16, 0)
  1195.         if A_Gui
  1196.             gw := A_GuiWidth, gh := A_GuiHeight
  1197.         else
  1198.         {
  1199.             DllCall("GetClientRect", "UPtr", this.hMainWindow, "Ptr", &RECT, "UInt")
  1200.             gw := NumGet(RECT, 8, "Int"), gh := NumGet(RECT, 12, "Int")
  1201.         }
  1202.         gtw := 3 + Round(this.Settings.Gutter.Width) * (this.ZoomLevel ? this.ZoomLevel : 1), sbh := this.StatusBarHeight
  1203.         GuiControl, Move, % this.RichCode.hWnd, % "x" 0+gtw "y" 0         "w" gw-gtw "h" gh-28-sbh
  1204.         if this.Settings.Gutter.Width
  1205.             GuiControl, Move, % this.hGutter  , % "x" 0     "y" 0         "w" gtw    "h" gh-28-sbh
  1206.         GuiControl, Move, % this.hRunButton   , % "x" 0     "y" gh-28-sbh "w" gw     "h" 28
  1207.     }
  1208.    
  1209.     GuiDropFiles(hWnd, Files)
  1210.     {
  1211.         ; TODO: support multiple file drop
  1212.         this.LoadCode(FileOpen(Files[1], "r").Read(), Files[1])
  1213.     }
  1214.    
  1215.     GuiClose()
  1216.     {
  1217.         if this.RichCode.Modified
  1218.         {
  1219.             Gui, +OwnDialogs
  1220.             MsgBox, 308, % this.Title " - Confirm Exit", There are unsaved changes. Are you sure you want to exit?
  1221.             IfMsgBox, No
  1222.                 return true
  1223.         }
  1224.        
  1225.         if (this.Exec.Status == 0) ; Running
  1226.         {
  1227.             SetTimer(this.Bound.CheckIfRunning, "Delete")
  1228.             this.Exec.Terminate()
  1229.         }
  1230.        
  1231.         ; Free up the AC class
  1232.         this.AC := ""
  1233.        
  1234.         ; Release wm_message hooks
  1235.         for each, Msg in [0x100, 0x201, 0x202, 0x204] ; WM_KEYDOWN, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN
  1236.             OnMessage(Msg, this.Bound.OnMessage, 0)
  1237.        
  1238.         ; Delete timers
  1239.         SetTimer(this.Bound.SyncGutter, "Delete")
  1240.         SetTimer(this.Bound.GuiSize, "Delete")
  1241.        
  1242.         ; Break all the BoundFunc circular references
  1243.         this.Delete("Bound")
  1244.        
  1245.         ; Release WinEvents handler
  1246.         WinEvents.Unregister(this.hMainWindow)
  1247.        
  1248.         ; Release GUI window and control glabels
  1249.         Gui, Destroy
  1250.        
  1251.         ; Release menu bar (Has to be done after Gui, Destroy)
  1252.         for each, MenuName in this.Menus
  1253.             Menu, %MenuName%, DeleteAll
  1254.        
  1255.         this.CloseCallback()
  1256.     }
  1257.    
  1258.     class Paste
  1259.     {
  1260.         static Targets := {"IRC": "#ahk", "Discord": "discord"}
  1261.        
  1262.         __New(Parent)
  1263.         {
  1264.             this.Parent := Parent
  1265.            
  1266.             ParentWnd := this.Parent.hMainWindow
  1267.             Gui, New, +Owner%ParentWnd% +ToolWindow +hWndhWnd
  1268.             this.hWnd := hWnd
  1269.             Gui, Margin, 5, 5
  1270.            
  1271.             Gui, Add, Text, xm ym w30 h22 +0x200, Desc: ; 0x200 for vcenter
  1272.             Gui, Add, Edit, x+5 yp w125 h22 hWndhPasteDesc, % this.Parent.Settings.DefaultDesc
  1273.             this.hPasteDesc := hPasteDesc
  1274.            
  1275.             Gui, Add, Button, x+4 yp-1 w52 h24 Default hWndhPasteButton, Paste
  1276.             this.hPasteButton := hPasteButton
  1277.             BoundPaste := this.Paste.Bind(this)
  1278.             GuiControl, +g, %hPasteButton%, %BoundPaste%
  1279.            
  1280.             Gui, Add, Text, xm y+5 w30 h22 +0x200, Name: ; 0x200 for vcenter
  1281.             Gui, Add, Edit, x+5 yp w100 h22 hWndhPasteName, % this.Parent.Settings.DefaultName
  1282.             this.hPasteName := hPasteName
  1283.            
  1284.             Gui, Add, DropDownList, x+5 yp w75 hWndhPasteChan, Announce||IRC|Discord
  1285.             this.hPasteChan := hPasteChan
  1286.            
  1287.             PostMessage, 0x153, -1, 22-6,, ahk_id %hPasteChan% ; Set height of ComboBox
  1288.             Gui, Show,, % this.Parent.Title " - Pastebin"
  1289.            
  1290.             WinEvents.Register(this.hWnd, this)
  1291.         }
  1292.        
  1293.         GuiClose()
  1294.         {
  1295.             GuiControl, -g, % this.hPasteButton
  1296.             WinEvents.Unregister(this.hWnd)
  1297.             Gui, Destroy
  1298.         }
  1299.        
  1300.         GuiEscape()
  1301.         {
  1302.             this.GuiClose()
  1303.         }
  1304.        
  1305.         Paste()
  1306.         {
  1307.             GuiControlGet, PasteDesc,, % this.hPasteDesc
  1308.             GuiControlGet, PasteName,, % this.hPasteName
  1309.             GuiControlGet, PasteChan,, % this.hPasteChan
  1310.             this.GuiClose()
  1311.            
  1312.             Link := Ahkbin(this.Parent.RichCode.Value, PasteName, PasteDesc, this.Targets[PasteChan])
  1313.            
  1314.             MsgBox, 292, % this.Parent.Title " - Pasted", Link received:`n%Link%`n`nCopy to clipboard?
  1315.             IfMsgBox, Yes
  1316.                 Clipboard := Link
  1317.         }
  1318.     }
  1319.     class Publish
  1320.     {
  1321.         __New(Parent)
  1322.         {
  1323.             this.Parent := Parent
  1324.            
  1325.             ParentWnd := this.Parent.hMainWindow
  1326.             Gui, New, +Owner%ParentWnd% +ToolWindow +hWndhWnd
  1327.             this.hWnd := hWnd
  1328.             Gui, Margin, 5, 5
  1329.            
  1330.             ; 0x200 for vcenter
  1331.             Gui, Add, Text, w245 h22 Center +0x200, Gather all includes and save to file.
  1332.            
  1333.             Gui, Add, Checkbox, hWndhWnd w120 h22 Checked Section, Keep Comments
  1334.             this.hComments := hWnd
  1335.             Gui, Add, Checkbox, hWndhWnd w120 h22 Checked, Keep Indentation
  1336.             this.hIndent := hWnd
  1337.             Gui, Add, Checkbox, hWndhWnd w120 h22 Checked, Keep Empty Lines
  1338.             this.hEmpties := hWnd
  1339.            
  1340.             Gui, Add, Button, hWndhWnd w120 h81 ys-1 Default, Export
  1341.             this.hButton := hWnd
  1342.             BoundPublish := this.Publish.Bind(this)
  1343.             GuiControl, +g, %hWnd%, %BoundPublish%
  1344.            
  1345.             Gui, Show,, % this.Parent.Title " - Publish"
  1346.            
  1347.             WinEvents.Register(this.hWnd, this)
  1348.         }
  1349.        
  1350.         GuiClose()
  1351.         {
  1352.             GuiControl, -g, % this.hButton
  1353.             WinEvents.Unregister(this.hWnd)
  1354.             Gui, Destroy
  1355.         }
  1356.        
  1357.         GuiEscape()
  1358.         {
  1359.             this.GuiClose()
  1360.         }
  1361.        
  1362.         Publish()
  1363.         {
  1364.             GuiControlGet, KeepComments,, % this.hComments
  1365.             GuiControlGet, KeepIndent,, % this.hIndent
  1366.             GuiControlGet, KeepEmpties,, % this.hEmpties
  1367.             this.GuiClose()
  1368.            
  1369.             Gui, % this.Parent.hMainWindow ":+OwnDialogs"
  1370.             FileSelectFile, FilePath, S18,, % this.Parent.Title " - Publish Code"
  1371.             if ErrorLevel
  1372.                 return
  1373.            
  1374.             FileOpen(FilePath, "w").Write(this.Parent.RichCode.Value)
  1375.             PreprocessScript(Text, FilePath, KeepComments, KeepIndent, KeepEmpties)
  1376.             FileOpen(FilePath, "w").Write(Text)
  1377.         }
  1378.     }
  1379.     class Find
  1380.     {
  1381.         __New(Parent)
  1382.         {
  1383.             this.Parent := Parent
  1384.            
  1385.             ParentWnd := this.Parent.hMainWindow
  1386.             Gui, New, +Owner%ParentWnd% +ToolWindow +hWndhWnd
  1387.             this.hWnd := hWnd
  1388.             Gui, Margin, 5, 5
  1389.            
  1390.            
  1391.             ; Search
  1392.             Gui, Add, Edit, hWndhWnd w200
  1393.             SendMessage, 0x1501, True, &cue := "Search Text",, ahk_id %hWnd% ; EM_SETCUEBANNER
  1394.             this.hNeedle := hWnd
  1395.            
  1396.             Gui, Add, Button, yp-1 x+m w75 Default hWndhWnd, Find Next
  1397.             Bound := this.BtnFind.Bind(this)
  1398.             GuiControl, +g, %hWnd%, %Bound%
  1399.            
  1400.             Gui, Add, Button, yp x+m w75 hWndhWnd, Coun&t All
  1401.             Bound := this.BtnCount.Bind(this)
  1402.             GuiControl, +g, %hWnd%, %Bound%
  1403.            
  1404.            
  1405.             ; Replace
  1406.             Gui, Add, Edit, hWndhWnd w200 xm Section
  1407.             SendMessage, 0x1501, True, &cue := "Replacement",, ahk_id %hWnd% ; EM_SETCUEBANNER
  1408.             this.hReplace := hWnd
  1409.            
  1410.             Gui, Add, Button, yp-1 x+m w75 hWndhWnd, &Replace
  1411.             Bound := this.Replace.Bind(this)
  1412.             GuiControl, +g, %hWnd%, %Bound%
  1413.            
  1414.             Gui, Add, Button, yp x+m w75 hWndhWnd, Replace &All
  1415.             Bound := this.ReplaceAll.Bind(this)
  1416.             GuiControl, +g, %hWnd%, %Bound%
  1417.            
  1418.            
  1419.             ; Options
  1420.             Gui, Add, Checkbox, hWndhWnd xm, &Case Sensitive
  1421.             this.hOptCase := hWnd
  1422.             Gui, Add, Checkbox, hWndhWnd, Re&gular Expressions
  1423.             this.hOptRegEx := hWnd
  1424.             Gui, Add, Checkbox, hWndhWnd, Transform`, &Deref
  1425.             this.hOptDeref := hWnd
  1426.            
  1427.            
  1428.             Gui, Show,, % this.Parent.Title " - Find"
  1429.            
  1430.             WinEvents.Register(this.hWnd, this)
  1431.         }
  1432.        
  1433.         GuiClose()
  1434.         {
  1435.             GuiControl, -g, % this.hButton
  1436.             WinEvents.Unregister(this.hWnd)
  1437.             Gui, Destroy
  1438.         }
  1439.        
  1440.         GuiEscape()
  1441.         {
  1442.             this.GuiClose()
  1443.         }
  1444.        
  1445.         GetNeedle()
  1446.         {
  1447.             Opts := this.Case ? "`n" : "i`n"
  1448.             Opts .= this.Needle ~= "^[^\(]\)" ? "" : ")"
  1449.             if this.RegEx
  1450.                 return Opts . this.Needle
  1451.             else
  1452.                 return Opts "\Q" StrReplace(this.Needle, "\E", "\E\\E\Q") "\E"
  1453.         }
  1454.        
  1455.         Find(StartingPos:=1, WrapAround:=True)
  1456.         {
  1457.             Needle := this.GetNeedle()
  1458.            
  1459.             ; Search from StartingPos
  1460.             NextPos := RegExMatch(this.Haystack, Needle, Match, StartingPos)
  1461.            
  1462.             ; Search from the top
  1463.             if (!NextPos && WrapAround)
  1464.                 NextPos := RegExMatch(this.Haystack, Needle, Match)
  1465.            
  1466.             return NextPos ? [NextPos, NextPos+StrLen(Match)] : False
  1467.         }
  1468.        
  1469.         Submit()
  1470.         {
  1471.             ; Options
  1472.             GuiControlGet, Deref,, % this.hOptDeref
  1473.             GuiControlGet, Case,, % this.hOptCase
  1474.             this.Case := Case
  1475.             GuiControlGet, RegEx,, % this.hOptRegEx
  1476.             this.RegEx := RegEx
  1477.            
  1478.             ; Search Text/Needle
  1479.             GuiControlGet, Needle,, % this.hNeedle
  1480.             if Deref
  1481.                 Transform, Needle, Deref, %Needle%
  1482.             this.Needle := Needle
  1483.            
  1484.             ; Replacement
  1485.             GuiControlGet, Replace,, % this.hReplace
  1486.             if Deref
  1487.                 Transform, Replace, Deref, %Replace%
  1488.             this.Replace := Replace
  1489.            
  1490.             ; Haystack
  1491.             this.Haystack := StrReplace(this.Parent.RichCode.Value, "`r")
  1492.         }
  1493.        
  1494.         BtnFind()
  1495.         {
  1496.             Gui, +OwnDialogs
  1497.             this.Submit()
  1498.            
  1499.             ; Find and select the item or error out
  1500.             if (Pos := this.Find(this.Parent.RichCode.Selection[1]+2))
  1501.                 this.Parent.RichCode.Selection := [Pos[1] - 1, Pos[2] - 1]
  1502.             else
  1503.                 MsgBox, 0x30, % this.Parent.Title " - Find", Search text not found
  1504.         }
  1505.        
  1506.         BtnCount()
  1507.         {
  1508.             Gui, +OwnDialogs
  1509.             this.Submit()
  1510.            
  1511.             ; Find and count all instances
  1512.             Count := 0, Start := 1
  1513.             while (Pos := this.Find(Start, False))
  1514.                 Start := Pos[1]+1, Count += 1
  1515.            
  1516.             MsgBox, 0x40, % this.Parent.Title " - Find", %Count% instances found
  1517.         }
  1518.        
  1519.         Replace()
  1520.         {
  1521.             this.Submit()
  1522.            
  1523.             ; Get the current selection
  1524.             Sel := this.Parent.RichCode.Selection
  1525.            
  1526.             ; Find the next occurrence including the current selection
  1527.             Pos := this.Find(Sel[1]+1)
  1528.            
  1529.             ; If the found item is already selected
  1530.             if (Sel[1]+1 == Pos[1] && Sel[2]+1 == Pos[2])
  1531.             {
  1532.                 ; Replace it
  1533.                 this.Parent.RichCode.SelectedText := this.Replace
  1534.                
  1535.                 ; Update the haystack to include the replacement
  1536.                 this.Haystack := StrReplace(this.Parent.RichCode.Value, "`r")
  1537.                
  1538.                 ; Find the next item *not* including the current selection
  1539.                 Pos := this.Find(Sel[1]+StrLen(this.Replace)+1)
  1540.             }
  1541.            
  1542.             ; Select the next found item or error out
  1543.             if Pos
  1544.                 this.Parent.RichCode.Selection := [Pos[1] - 1, Pos[2] - 1]
  1545.             else
  1546.                 MsgBox, 0x30, % this.Parent.Title " - Find", No more instances found
  1547.         }
  1548.        
  1549.         ReplaceAll()
  1550.         {
  1551.             rc := this.Parent.RichCode
  1552.             this.Submit()
  1553.            
  1554.             Needle := this.GetNeedle()
  1555.            
  1556.             ; Replace the text in a way that pushes to the undo buffer
  1557.             rc.Frozen := True
  1558.             Sel := rc.Selection
  1559.             rc.Selection := [0, -1]
  1560.             rc.SelectedText := RegExReplace(this.Haystack, Needle, this.Replace, Count)
  1561.             rc.Selection := Sel
  1562.             rc.Frozen := False
  1563.            
  1564.             MsgBox, 0x40, % this.Parent.Title " - Find", %Count% instances replaced
  1565.         }
  1566.     }
  1567.     class ScriptOpts
  1568.     {
  1569.         __New(Parent)
  1570.         {
  1571.             this.Parent := Parent
  1572.            
  1573.             ; Create a GUI
  1574.             ParentWnd := this.Parent.hMainWindow
  1575.             Gui, New, +Owner%ParentWnd% +ToolWindow +hWndhWnd
  1576.             this.hWnd := hWnd
  1577.             WinEvents.Register(this.hWnd, this)
  1578.            
  1579.             ; Add path picker button
  1580.             Gui, Add, Button, xm ym w95 hWndhButton, Pick AHK Path
  1581.             BoundSelectFile := this.SelectFile.Bind(this)
  1582.             GuiControl, +g, %hButton%, %BoundSelectFile%
  1583.            
  1584.             ; Add path visualization field
  1585.             Gui, Add, Edit, ym w250 ReadOnly hWndhAhkPath, % this.Parent.Settings.AhkPath
  1586.             this.hAhkPath := hAhkPath
  1587.            
  1588.             ; Add parameters field
  1589.             Gui, Add, Text, xm w95 h22 +0x200, Parameters:
  1590.             Gui, Add, Edit, yp x+m w250 hWndhParamEdit
  1591.             this.hParamEdit := hParamEdit
  1592.             ParamEditBound := this.ParamEdit.Bind(this)
  1593.             GuiControl, +g, %hParamEdit%, %ParamEditBound%
  1594.            
  1595.             ; Add Working Directory field
  1596.             Gui, Add, Button, xm w95 hWndhWDButton, Pick Working Dir
  1597.             BoundSelectPath := this.SelectPath.Bind(this)
  1598.             GuiControl, +g, %hWDButton%, %BoundSelectPath%
  1599.            
  1600.             ; Add Working Dir visualization field
  1601.             Gui, Add, Edit, x+m w250 ReadOnly hWndhWorkingDir, %A_WorkingDir%
  1602.             this.hWorkingDir := hWorkingDir
  1603.            
  1604.             ; Show the GUI
  1605.             Gui, Show,, % this.Parent.Title " - Script Options"
  1606.         }
  1607.        
  1608.         ParamEdit()
  1609.         {
  1610.             GuiControlGet, ParamEdit,, % this.hParamEdit
  1611.             this.Parent.Settings.Params := ParamEdit
  1612.         }
  1613.        
  1614.         SelectFile()
  1615.         {
  1616.             GuiControlGet, AhkPath,, % this.hAhkPath
  1617.             FileSelectFile, AhkPath, 1, %AhkPath%, Pick an AHK EXE, Executables (*.exe)
  1618.             if !AhkPath
  1619.                 return
  1620.             this.Parent.Settings.AhkPath := AhkPath
  1621.             GuiControl,, % this.hAhkPath, %AhkPath%
  1622.         }
  1623.        
  1624.         SelectPath()
  1625.         {
  1626.             FileSelectFolder, WorkingDir, *%A_WorkingDir%, 0, Choose the Working Directory
  1627.             if !WorkingDir
  1628.                 return
  1629.             SetWorkingDir, %WorkingDir%
  1630.             this.UpdateFields()
  1631.         }
  1632.        
  1633.         UpdateFields()
  1634.         {
  1635.             GuiControl,, % this.hWorkingDir, %A_WorkingDir%
  1636.         }
  1637.        
  1638.         GuiClose()
  1639.         {
  1640.             WinEvents.Unregister(this.hWnd)
  1641.             Gui, Destroy
  1642.         }
  1643.        
  1644.         GuiEscape()
  1645.         {
  1646.             this.GuiClose()
  1647.         }
  1648.     }
  1649.     class MenuButtons
  1650.     {
  1651.         __New(Parent)
  1652.         {
  1653.             this.Parent := Parent
  1654.         }
  1655.        
  1656.         Save(SaveAs)
  1657.         {
  1658.             if (SaveAs || !this.Parent.FilePath)
  1659.             {
  1660.                 Gui, +OwnDialogs
  1661.                 FileSelectFile, FilePath, S18,, % this.Parent.Title " - Save Code"
  1662.                 if ErrorLevel
  1663.                     return
  1664.                 this.Parent.FilePath := FilePath
  1665.             }
  1666.            
  1667.             FileOpen(this.Parent.FilePath, "w").Write(this.Parent.RichCode.Value)
  1668.            
  1669.             this.Parent.RichCode.Modified := False
  1670.             this.Parent.UpdateStatusBar()
  1671.         }
  1672.        
  1673.         Rename()
  1674.         {
  1675.             ; Make sure the opened file still exists
  1676.             if !InStr(FileExist(this.Parent.FilePath), "A")
  1677.                 throw Exception("Opened file no longer exists")
  1678.            
  1679.             ; Ask where to move it to
  1680.             FileSelectFile, FilePath, S10, % this.Parent.FilePath
  1681.             , Rename As, AutoHotkey Scripts (*.ahk)
  1682.             if InStr(FileExist(FilePath), "A")
  1683.                 throw Exception("Destination file already exists")
  1684.            
  1685.             ; Attempt to move it
  1686.             FileMove, % this.Parent.FilePath, % FilePath
  1687.             if ErrorLevel
  1688.                 throw Exception("Failed to rename file")
  1689.             this.Parent.FilePath := FilePath
  1690.         }
  1691.        
  1692.         Open()
  1693.         {
  1694.             Gui, +OwnDialogs
  1695.             FileSelectFile, FilePath, 3,, % this.Parent.Title " - Open Code"
  1696.             if ErrorLevel
  1697.                 return
  1698.             this.Parent.LoadCode(FileOpen(FilePath, "r").Read(), FilePath)
  1699.            
  1700.             ; Follow the directory of the most recently opened file
  1701.             SetWorkingDir, %FilePath%\..
  1702.             this.Parent.ScriptOpts.UpdateFields()
  1703.         }
  1704.        
  1705.         OpenFolder()
  1706.         {
  1707.             Run, explorer.exe "%A_WorkingDir%"
  1708.         }
  1709.        
  1710.         New()
  1711.         {
  1712.             Run, "%A_AhkPath%" "%A_ScriptFullPath%"
  1713.         }
  1714.        
  1715.         Publish()
  1716.         { ; TODO: Recycle PubInstance
  1717.             if WinExist("ahk_id" this.PubInstance.hWnd)
  1718.                 WinActivate, % "ahk_id" this.PubInstance.hWnd
  1719.             else
  1720.                 this.PubInstance := new this.Parent.Publish(this.Parent)
  1721.         }
  1722.        
  1723.         Fetch()
  1724.         {
  1725.             Gui, +OwnDialogs
  1726.             InputBox, Url, % this.Parent.Title " - Fetch Code", Enter a URL to fetch code from.
  1727.             if (Url := Trim(Url))
  1728.                 this.Parent.LoadCode(UrlDownloadToVar(Url))
  1729.         }
  1730.        
  1731.         Find()
  1732.         { ; TODO: Recycle FindInstance
  1733.             if WinExist("ahk_id" this.FindInstance.hWnd)
  1734.                 WinActivate, % "ahk_id" this.FindInstance.hWnd
  1735.             else
  1736.                 this.FindInstance := new this.Parent.Find(this.Parent)
  1737.         }
  1738.        
  1739.         Paste()
  1740.         { ; TODO: Recycle PasteInstance
  1741.             if WinExist("ahk_id" this.PasteInstance.hWnd)
  1742.                 WinActivate, % "ahk_id" this.PasteInstance.hWnd
  1743.             else
  1744.                 this.PasteInstance := new this.Parent.Paste(this.Parent)
  1745.         }
  1746.        
  1747.         ScriptOpts()
  1748.         {
  1749.             if WinExist("ahk_id" this.Parent.ScriptOptsInstance.hWnd)
  1750.                 WinActivate, % "ahk_id" this.Parent.ScriptOptsInstance.hWnd
  1751.             else
  1752.                 this.Parent.ScriptOptsInstance := new this.Parent.ScriptOpts(this.Parent)
  1753.         }
  1754.        
  1755.         ToggleOnTop()
  1756.         {
  1757.             if (this.Parent.AlwaysOnTop := !this.Parent.AlwaysOnTop)
  1758.             {
  1759.                 Menu, % this.Parent.Menus[4], Check, &AlwaysOnTop`tAlt+A
  1760.                 Gui, +AlwaysOnTop
  1761.             }
  1762.             else
  1763.             {
  1764.                 Menu, % this.Parent.Menus[4], Uncheck, &AlwaysOnTop`tAlt+A
  1765.                 Gui, -AlwaysOnTop
  1766.             }
  1767.         }
  1768.        
  1769.         Highlighter()
  1770.         {
  1771.             if (this.Parent.Settings.UseHighlighter := !this.Parent.Settings.UseHighlighter)
  1772.                 Menu, % this.Parent.Menus[4], Check, &Highlighter
  1773.             else
  1774.                 Menu, % this.Parent.Menus[4], Uncheck, &Highlighter
  1775.            
  1776.             ; Force refresh the code, adding/removing any highlighting
  1777.             this.Parent.RichCode.Value := this.Parent.RichCode.Value
  1778.         }
  1779.        
  1780.         GlobalRun()
  1781.         {
  1782.             if (this.Parent.Settings.GlobalRun := !this.Parent.Settings.GlobalRun)
  1783.                 Menu, % this.Parent.Menus[4], Check, Global Run Hotkeys
  1784.             else
  1785.                 Menu, % this.Parent.Menus[4], Uncheck, Global Run Hotkeys
  1786.         }
  1787.        
  1788.         AutoIndent()
  1789.         {
  1790.             this.Parent.LoadCode(AutoIndent(this.Parent.RichCode.Value
  1791.             , this.Parent.Settings.Indent), this.Parent.FilePath)
  1792.         }
  1793.        
  1794.         Help()
  1795.         {
  1796.             HelpFile.Open(this.Parent.GetKeywordFromCaret())
  1797.         }
  1798.        
  1799.         About()
  1800.         {
  1801.             Gui, +OwnDialogs
  1802.             MsgBox,, % this.Parent.Title " - About", CodeQuickTester written by GeekDude
  1803.         }
  1804.        
  1805.         ServiceHandler()
  1806.         {
  1807.             Gui, +OwnDialogs
  1808.             if ServiceHandler.Installed()
  1809.             {
  1810.                 MsgBox, 36, % this.Parent.Title " - Uninstall Service Handler"
  1811.                 , Are you sure you want to remove CodeQuickTester from being the default service handler for "ahk:" links?
  1812.                 IfMsgBox, Yes
  1813.                 {
  1814.                     ServiceHandler.Remove()
  1815.                     Menu, % this.Parent.Menus[4], Uncheck, Install Service Handler
  1816.                 }
  1817.             }
  1818.             else
  1819.             {
  1820.                 MsgBox, 36, % this.Parent.Title " - Install Service Handler"
  1821.                 , Are you sure you want to install CodeQuickTester as the default service handler for "ahk:" links?
  1822.                 IfMsgBox, Yes
  1823.                 {
  1824.                     ServiceHandler.Install()
  1825.                     Menu, % this.Parent.Menus[4], Check, Install Service Handler
  1826.                 }
  1827.             }
  1828.         }
  1829.        
  1830.         DefaultEditor()
  1831.         {
  1832.             Gui, +OwnDialogs
  1833.            
  1834.             if !A_IsAdmin
  1835.             {
  1836.                 MsgBox, 48, % this.Parent.Title " - Change Editor", You must be running as administrator to use this feature.
  1837.                 return
  1838.             }
  1839.            
  1840.             RegRead, Editor, HKCR, AutoHotkeyScript\Shell\Edit\Command
  1841.             if (Editor == this.Parent.EditorString)
  1842.             {
  1843.                 MsgBox, 36, % this.Parent.Title " - Remove as Default Editor"
  1844.                 , % "Are you sure you want to restore the original default editor for .ahk files?"
  1845.                 . "`n`n" this.Parent.OrigEditorString
  1846.                 IfMsgBox, Yes
  1847.                 {
  1848.                     RegWrite REG_SZ, HKCR, AutoHotkeyScript\Shell\Edit\Command,, % this.Parent.OrigEditorString
  1849.                     Menu, % this.Parent.Menus[4], Uncheck, Set as Default Editor
  1850.                 }
  1851.             }
  1852.             else
  1853.             {
  1854.                 MsgBox, 36, % this.Parent.Title " - Set as Default Editor"
  1855.                 , % "Are you sure you want to install CodeQuickTester as the default editor for .ahk files?"
  1856.                 . "`n`n" this.Parent.EditorString
  1857.                 IfMsgBox, Yes
  1858.                 {
  1859.                     RegWrite REG_SZ, HKCR, AutoHotkeyScript\Shell\Edit\Command,, % this.Parent.EditorString
  1860.                     MsgBox, %ErrorLevel%
  1861.                     Menu, % this.Parent.Menus[4], Check, Set as Default Editor
  1862.                 }
  1863.             }
  1864.            
  1865.            
  1866.         }
  1867.        
  1868.         Comment()
  1869.         {
  1870.             this.Parent.RichCode.IndentSelection(False, ";")
  1871.         }
  1872.        
  1873.         Uncomment()
  1874.         {
  1875.             this.Parent.RichCode.IndentSelection(True, ";")
  1876.         }
  1877.        
  1878.         Indent()
  1879.         {
  1880.             this.Parent.RichCode.IndentSelection()
  1881.         }
  1882.        
  1883.         Unindent()
  1884.         {
  1885.             this.Parent.RichCode.IndentSelection(True)
  1886.         }
  1887.        
  1888.         IncludeRel()
  1889.         {
  1890.             FileSelectFile, AbsPath, 1,, Pick a script to include, AutoHotkey Scripts (*.ahk)
  1891.             if ErrorLevel
  1892.                 return
  1893.            
  1894.             ; Get the relative path
  1895.             VarSetCapacity(RelPath, A_IsUnicode?520:260) ; MAX_PATH
  1896.             if !DllCall("Shlwapi.dll\PathRelativePathTo"
  1897.                 , "Str", RelPath                    ; Out Directory
  1898.                 , "Str", A_WorkingDir, "UInt", 0x10 ; From Directory
  1899.                 , "Str", AbsPath, "UInt", 0x10)     ; To Directory
  1900.                 throw Exception("Relative path could not be found")
  1901.            
  1902.             ; Select the start of the line
  1903.             RC := this.Parent.RichCode
  1904.             Top := RC.SendMsg(0x436, 0, RC.Selection[1]) ; EM_EXLINEFROMCHAR
  1905.             TopLineIndex := RC.SendMsg(0xBB, Top, 0) ; EM_LINEINDEX
  1906.             RC.Selection := [TopLineIndex, TopLineIndex]
  1907.            
  1908.             ; Insert the include
  1909.             RC.SelectedText := "#Include " RelPath "`n"
  1910.             RC.Selection[1] := RC.Selection[2]
  1911.         }
  1912.        
  1913.         IncludeAbs()
  1914.         {
  1915.             FileSelectFile, AbsPath, 1,, Pick a script to include, AutoHotkey Scripts (*.ahk)
  1916.             if ErrorLevel
  1917.                 return
  1918.            
  1919.             ; Select the start of the line
  1920.             RC := this.Parent.RichCode
  1921.             Top := RC.SendMsg(0x436, 0, RC.Selection[1]) ; EM_EXLINEFROMCHAR
  1922.             TopLineIndex := RC.SendMsg(0xBB, Top, 0) ; EM_LINEINDEX
  1923.             RC.Selection := [TopLineIndex, TopLineIndex]
  1924.            
  1925.             ; Insert the include
  1926.             RC.SelectedText := "#Include " AbsPath "`n"
  1927.             RC.Selection[1] := RC.Selection[2]
  1928.         }
  1929.        
  1930.         AutoComplete()
  1931.         {
  1932.             if (this.Parent.Settings.UseAutoComplete := !this.Parent.Settings.UseAutoComplete)
  1933.                 Menu, % this.Parent.Menus[4], Check, AutoComplete
  1934.             else
  1935.                 Menu, % this.Parent.Menus[4], Uncheck, AutoComplete
  1936.            
  1937.             this.Parent.AC.Enabled := this.Parent.Settings.UseAutoComplete
  1938.         }
  1939.     }
  1940.     /*
  1941.         Implements functionality necessary for AutoCompletion of keywords in the
  1942.         RichCode control. Currently works off of values stored in the provided
  1943.         Parent object, but could be modified to work off a provided RichCode
  1944.         instance directly.
  1945.        
  1946.         The class is mostly self contained and could be easily extended to other
  1947.         projects, and even other types of controls. The main method of interacting
  1948.         with the class is by passing it WM_KEYDOWN messages. Another way to interact
  1949.         is by modifying the Fragment property, especially to clear it when you want
  1950.         to cancel autocompletion.
  1951.        
  1952.         Depends on CQT.ahk, and optionally on HelpFile.ahk
  1953.     */
  1954.    
  1955.     class AutoComplete
  1956.     {
  1957.         ; Maximum number of suggestions to be displayed in the dialog
  1958.         static MaxSuggestions := 9
  1959.        
  1960.         ; Minimum length for a word to be entered into the word list
  1961.         static MinWordLen := 4
  1962.        
  1963.         ; Minimum length of fragment before suggestions should be displayed
  1964.         static MinSuggestLen := 3
  1965.        
  1966.         ; Stores the initial caret position for newly typed fragments
  1967.         static CaretX := 0, CaretY := 0
  1968.        
  1969.        
  1970.         ; --- Properties ---
  1971.        
  1972.         Fragment[]
  1973.         {
  1974.             get
  1975.             {
  1976.                 return this._Fragment
  1977.             }
  1978.            
  1979.             set
  1980.             {
  1981.                 this._Fragment := Value
  1982.                
  1983.                 ; Give suggestions when a fragment of sufficient
  1984.                 ; length has been provided
  1985.                 if (StrLen(this._Fragment) >= 3)
  1986.                     this._Suggest()
  1987.                 else
  1988.                     this._Hide()
  1989.                
  1990.                 return this._Fragment
  1991.             }
  1992.         }
  1993.        
  1994.         Enabled[]
  1995.         {
  1996.             get
  1997.             {
  1998.                 return this._Enabled
  1999.             }
  2000.            
  2001.             set
  2002.             {
  2003.                 this._Enabled := Value
  2004.                 if (Value)
  2005.                     this.BuildWordList()
  2006.                 else
  2007.                     this.Fragment := ""
  2008.                 return Value
  2009.             }
  2010.         }
  2011.        
  2012.        
  2013.         ; --- Constructor, Destructor ---
  2014.        
  2015.         __New(Parent, Enabled:=True)
  2016.         {
  2017.             this.Parent := Parent
  2018.             this.Enabled := Enabled
  2019.             this.WineVer := DllCall("ntdll.dll\wine_get_version", "AStr")
  2020.            
  2021.             ; Create the tool GUI for the floating list
  2022.             hParentWnd := this.Parent.hMainWindow
  2023.             Gui, +hWndhDefaultWnd
  2024.             Relation := this.WineVer ? "Parent" Parent.RichCode.hWnd : "Owner" Parent.hMainWindow
  2025.             Gui, New, +%Relation% -Caption +ToolWindow +hWndhWnd
  2026.             this.hWnd := hWnd
  2027.             Gui, Margin, 0, 0
  2028.            
  2029.             ; Create the ListBox control withe appropriate font and styling
  2030.             Font := this.Parent.Settings.Font
  2031.             Gui, Font, % "s" Font.Size, % Font.Typeface
  2032.             Gui, Add, ListBox, x0 y0 r1 0x100 AltSubmit hWndhListBox, Item
  2033.             this.hListBox := hListBox
  2034.            
  2035.             ; Finish GUI creation and restore the default GUI
  2036.             Gui, Show, Hide, % this.Parent.Title " - AutoComplete"
  2037.             Gui, %hDefaultWnd%:Default
  2038.            
  2039.             ; Get relevant dimensions of the ListBox for later resizing
  2040.             SendMessage, 0x1A1, 0, 0,, % "ahk_id" this.hListBox ; LB_GETITEMHEIGHT
  2041.             this.ListBoxItemHeight := ErrorLevel
  2042.             VarSetCapacity(ListBoxRect, 16, 0)
  2043.             DllCall("User32.dll\GetClientRect", "Ptr", this.hListBox, "Ptr", &ListBoxRect)
  2044.             this.ListBoxMargins := NumGet(ListBoxRect, 12, "Int") - this.ListBoxItemHeight
  2045.            
  2046.             ; Set up the GDI Device Context for later text measurement in _GetWidth
  2047.             this.hDC := DllCall("GetDC", "UPtr", this.hListBox, "UPtr")
  2048.             SendMessage, 0x31, 0, 0,, % "ahk_id" this.hListBox ; WM_GETFONT
  2049.             this.hFont := DllCall("SelectObject", "UPtr", this.hDC, "UPtr", ErrorLevel, "UPtr")
  2050.            
  2051.             ; Record the total screen width for later user. If the monitors get
  2052.             ; rearranged while the script is still running this value will be
  2053.             ; inaccurate. However, this will not likely be a significant issue,
  2054.             ; and the issues caused by it would be minimal.
  2055.             SysGet, ScreenWidth, 78
  2056.             this.ScreenWidth := ScreenWidth
  2057.            
  2058.             ; Pull a list of default words from the help file.
  2059.             ; TODO: Include some kind of hard-coded list for when the help file is
  2060.             ;       not present, or to supplement the help file.
  2061.             for Key in HelpFile.GetLookup()
  2062.                 this.DefaultWordList .= "|" LTrim(Key, "#")
  2063.            
  2064.             ; Build the initial word list based on the default words and the
  2065.             ; RichCode's contents at the time of AutoComplete's initialization
  2066.             this.BuildWordList()
  2067.         }
  2068.        
  2069.         __Delete()
  2070.         {
  2071.             Gui, % this.hWnd ":Destroy"
  2072.             this.Visible := False
  2073.             DllCall("SelectObject", "UPtr", this.hDC, "UPtr", this.hFont, "UPtr")
  2074.             DllCall("ReleaseDC", "UPtr", this.hListBox, "UPtr", this.hDC)
  2075.         }
  2076.        
  2077.        
  2078.         ; --- Private Methods ---
  2079.        
  2080.         ; Gets the pixel-based width of a provided text snippet using the GDI font
  2081.         ; selected into the ListBox control
  2082.         _GetWidth(Text)
  2083.         {
  2084.             MaxWidth := 0
  2085.             Loop, Parse, Text, |
  2086.             {
  2087.                 DllCall("GetTextExtentPoint32", "UPtr", this.hDC, "Str", A_LoopField
  2088.                 , "Int", StrLen(A_LoopField), "Int64*", Size), Size &= 0xFFFFFFFF
  2089.                
  2090.                 if (Size > MaxWidth)
  2091.                     MaxWidth := Size
  2092.             }
  2093.            
  2094.             return MaxWidth
  2095.         }
  2096.        
  2097.         ; Shows the suggestion dialog with contents of the provided DisplayList
  2098.         _Show(DisplayList)
  2099.         {
  2100.             ; Insert the new list
  2101.             GuiControl,, % this.hListBox, %DisplayList%
  2102.             GuiControl, Choose, % this.hListBox, 1
  2103.            
  2104.             ; Resize to fit contents
  2105.             StrReplace(DisplayList, "|",, Rows)
  2106.             Height := Rows * this.ListBoxItemHeight + this.ListBoxMargins
  2107.             Width := this._GetWidth(DisplayList) + 10
  2108.             GuiControl, Move, % this.hListBox, w%Width% h%Height%
  2109.            
  2110.             ; Keep the dialog from running off the screen
  2111.             X := this.CaretX, Y := this.CaretY + 20
  2112.             if ((X + Width) > this.ScreenWidth)
  2113.                 X := this.ScreenWidth - Width
  2114.            
  2115.             ; Make the dialog visible
  2116.             Gui, % this.hWnd ":Show", x%X% y%Y% AutoSize NoActivate
  2117.             this.Visible := True
  2118.         }
  2119.        
  2120.         ; Hides the dialog if it is visible
  2121.         _Hide()
  2122.         {
  2123.             if !this.Visible
  2124.                 return
  2125.            
  2126.             Gui, % this.hWnd ":Hide"
  2127.             this.Visible := False
  2128.         }
  2129.        
  2130.         ; Filters the word list for entries starting with the fragment, then
  2131.         ; shows the dialog with the filtered list as suggestions
  2132.         _Suggest()
  2133.         {
  2134.             ; Filter the list for words beginning with the fragment
  2135.             Suggestions := LTrim(RegExReplace(this.WordList
  2136.             , "i)\|(?!" this.Fragment ")[^\|]+"), "|")
  2137.            
  2138.             ; Fail out if there were no matches
  2139.             if !Suggestions
  2140.                 return true, this._Hide()
  2141.            
  2142.             ; Pull the first MaxSuggestions suggestions
  2143.             if (Pos := InStr(Suggestions, "|",,, this.MaxSuggestions))
  2144.                 Suggestions := SubStr(Suggestions, 1, Pos-1)
  2145.             this.Suggestions := Suggestions
  2146.            
  2147.             this._Show("|" Suggestions)
  2148.         }
  2149.        
  2150.         ; Finishes the fragment with the selected suggestion
  2151.         _Complete()
  2152.         {
  2153.             ; Get the text of the selected item
  2154.             GuiControlGet, Selected,, % this.hListBox
  2155.             Suggestion := StrSplit(this.Suggestions, "|")[Selected]
  2156.            
  2157.             ; Replace fragment preceding cursor with selected suggestion
  2158.             RC := this.Parent.RichCode
  2159.             RC.Selection[1] -= StrLen(this.Fragment)
  2160.             RC.SelectedText := Suggestion
  2161.             RC.Selection[1] := RC.Selection[2]
  2162.            
  2163.             ; Clear out the fragment in preparation for further typing
  2164.             this.Fragment := ""
  2165.         }
  2166.        
  2167.        
  2168.         ; --- Public Methods ---
  2169.        
  2170.         ; Interpret WM_KEYDOWN messages, the primary means of interfacing with the
  2171.         ; class. These messages can be provided by registering an appropriate
  2172.         ; handler with OnMessage, or by forwarding the events from another handler
  2173.         ; for the control.
  2174.         WM_KEYDOWN(wParam, lParam)
  2175.         {
  2176.             if (!this._Enabled)
  2177.                 return
  2178.            
  2179.             ; Get the name of the key using the virtual key code. The key's scan
  2180.             ; code is not used here, but is available in bits 16-23 of lParam and
  2181.             ; could be used in future versions for greater reliability.
  2182.             Key := GetKeyName(Format("vk{:02x}", wParam))
  2183.            
  2184.             ; Treat Numpad variants the same as the equivalent standard keys
  2185.             Key := StrReplace(Key, "Numpad")
  2186.            
  2187.             ; Handle presses meant to interact with the dialog, such as
  2188.             ; navigational, confirmational, or dismissive commands.
  2189.             if (this.Visible)
  2190.             {
  2191.                 if (Key == "Tab" || Key == "Enter")
  2192.                     return False, this._Complete()
  2193.                 else if (Key == "Up")
  2194.                     return False, this.SelectUp()
  2195.                 else if (Key == "Down")
  2196.                     return False, this.SelectDown()
  2197.             }
  2198.            
  2199.             ; Ignore standalone modifier presses, and some modified regular presses
  2200.             if Key in Shift,Control,Alt
  2201.                 return
  2202.            
  2203.             ; Reset on presses with the control modifier
  2204.             if GetKeyState("Control")
  2205.                 return "", this.Fragment := ""
  2206.            
  2207.             ; Subtract from the end of fragment on backspace
  2208.             if (Key == "Backspace")
  2209.                 return "", this.Fragment := SubStr(this.Fragment, 1, -1)
  2210.            
  2211.             ; Apply Shift and CapsLock
  2212.             if GetKeyState("Shift")
  2213.                 Key := StrReplace(Key, "-", "_")
  2214.             if (GetKeyState("Shift") ^ GetKeyState("CapsLock", "T"))
  2215.                 Key := Format("{:U}", Key)
  2216.            
  2217.             ; Reset on unwanted presses -- Allow numbers but not at beginning
  2218.             if !(Key ~= "^[A-Za-z_]$" || (this.Fragment != "" && Key ~= "^[0-9]$"))
  2219.                 return "", this.Fragment := ""
  2220.            
  2221.             ; Record the starting position of new fragments
  2222.             if (this.Fragment == "")
  2223.             {
  2224.                 CoordMode, Caret, % this.WineVer ? "Client" : "Screen"
  2225.                
  2226.                 ; Round "" to 0, which can prevent errors in the unlikely case that
  2227.                 ; input is received while the control is not focused.
  2228.                 this.CaretX := Round(A_CaretX), this.CaretY := Round(A_CaretY)
  2229.             }
  2230.            
  2231.             ; Update fragment with the press
  2232.             this.Fragment .= Key
  2233.         }
  2234.        
  2235.         ; Triggers a rebuild of the word list from the RichCode control's contents
  2236.         BuildWordList()
  2237.         {
  2238.             if (!this._Enabled)
  2239.                 return
  2240.            
  2241.             ; Replace non-word chunks with delimiters
  2242.             List := RegExReplace(this.Parent.RichCode.Value, "\W+", "|")
  2243.            
  2244.             ; Ignore numbers at the beginning of words
  2245.             List := RegExReplace(List, "\b[0-9]+")
  2246.            
  2247.             ; Ignore words that are too small
  2248.             List := RegExReplace(List, "\b\w{1," this.MinWordLen-1 "}\b")
  2249.            
  2250.             ; Append default entries, remove duplicates, and save the list
  2251.             List .= this.DefaultWordList
  2252.             Sort, List, U D| Z
  2253.             this.WordList := "|" Trim(List, "|")
  2254.         }
  2255.        
  2256.         ; Moves the selected item in the dialog up one position
  2257.         SelectUp()
  2258.         {
  2259.             GuiControlGet, Selected,, % this.hListBox
  2260.             if (--Selected < 1)
  2261.                 Selected := this.MaxSuggestions
  2262.             GuiControl, Choose, % this.hListBox, %Selected%
  2263.         }
  2264.        
  2265.         ; Moves the selected item in the dialog down one position
  2266.         SelectDown()
  2267.         {
  2268.             GuiControlGet, Selected,, % this.hListBox
  2269.             if (++Selected > this.MaxSuggestions)
  2270.                 Selected := 1
  2271.             GuiControl, Choose, % this.hListBox, %Selected%
  2272.         }
  2273.     }
  2274. }
  2275. class ServiceHandler ; static class
  2276. {
  2277.     static Protocol := "ahk"
  2278.    
  2279.     Install()
  2280.     {
  2281.         Protocol := this.Protocol
  2282.         RegWrite, REG_SZ, HKCU, Software\Classes\%Protocol%,, URL:AHK Script Protocol
  2283.         RegWrite, REG_SZ, HKCU, Software\Classes\%Protocol%, URL Protocol
  2284.         RegWrite, REG_SZ, HKCU, Software\Classes\%Protocol%\shell\open\command,, "%A_AhkPath%" "%A_ScriptFullPath%" "`%1"
  2285.     }
  2286.    
  2287.     Remove()
  2288.     {
  2289.         Protocol := this.Protocol
  2290.         RegDelete, HKCU, Software\Classes\%Protocol%
  2291.     }
  2292.    
  2293.     Installed()
  2294.     {
  2295.         Protocol := this.Protocol
  2296.         RegRead, Out, HKCU, Software\Classes\%Protocol%
  2297.         return !ErrorLevel
  2298.     }
  2299. }
  2300. class WinEvents ; static class
  2301. {
  2302.     static _ := WinEvents.AutoInit()
  2303.    
  2304.     AutoInit()
  2305.     {
  2306.         this.Table := []
  2307.         OnMessage(2, this.Destroy.bind(this))
  2308.     }
  2309.    
  2310.     Register(ID, HandlerClass, Prefix="Gui")
  2311.     {
  2312.         Gui, %ID%: +hWndhWnd +LabelWinEvents_
  2313.         this.Table[hWnd] := {Class: HandlerClass, Prefix: Prefix}
  2314.     }
  2315.    
  2316.     Unregister(ID)
  2317.     {
  2318.         Gui, %ID%: +hWndhWnd
  2319.         this.Table.Delete(hWnd)
  2320.     }
  2321.    
  2322.     Dispatch(Type, Params*)
  2323.     {
  2324.         Info := this.Table[Params[1]]
  2325.         return (Info.Class)[Info.Prefix . Type](Params*)
  2326.     }
  2327.    
  2328.     Destroy(wParam, lParam, Msg, hWnd)
  2329.     {
  2330.         this.Table.Delete(hWnd)
  2331.     }
  2332. }
  2333.  
  2334. WinEvents_Close(Params*) {
  2335.     return WinEvents.Dispatch("Close", Params*)
  2336. } WinEvents_Escape(Params*) {
  2337.     return WinEvents.Dispatch("Escape", Params*)
  2338. } WinEvents_Size(Params*) {
  2339.     return WinEvents.Dispatch("Size", Params*)
  2340. } WinEvents_ContextMenu(Params*) {
  2341.     return WinEvents.Dispatch("ContextMenu", Params*)
  2342. } WinEvents_DropFiles(Params*) {
  2343.     return WinEvents.Dispatch("DropFiles", Params*)
  2344. }
  2345. AutoIndent(Code, Indent = "`t", Newline = "`r`n")
  2346. {
  2347.     IndentRegEx =
  2348.     ( LTrim Join
  2349.     Catch|else|for|Finally|if|IfEqual|IfExist|
  2350.     IfGreater|IfGreaterOrEqual|IfInString|
  2351.     IfLess|IfLessOrEqual|IfMsgBox|IfNotEqual|
  2352.     IfNotExist|IfNotInString|IfWinActive|IfWinExist|
  2353.     IfWinNotActive|IfWinNotExist|Loop|Try|while
  2354.     )
  2355.    
  2356.     ; Lock and Block are modified ByRef by Current
  2357.     Lock := [], Block := []
  2358.     ParentIndent := Braces := 0
  2359.     ParentIndentObj := []
  2360.    
  2361.     for each, Line in StrSplit(Code, "`n", "`r")
  2362.     {
  2363.         Text := Trim(RegExReplace(Line, "\s;.*")) ; Comment removal
  2364.         First := SubStr(Text, 1, 1), Last := SubStr(Text, 0, 1)
  2365.         FirstTwo := SubStr(Text, 1, 2)
  2366.        
  2367.         IsExpCont := (Text ~= "i)^\s*(&&|OR|AND|\.|\,|\|\||:|\?)")
  2368.         IndentCheck := (Text ~= "iA)}?\s*\b(" IndentRegEx ")\b")
  2369.        
  2370.         if (First == "(" && Last != ")")
  2371.             Skip := True
  2372.         if (Skip)
  2373.         {
  2374.             if (First == ")")
  2375.                 Skip := False
  2376.             Out .= Newline . RTrim(Line)
  2377.             continue
  2378.         }
  2379.        
  2380.         if (FirstTwo == "*/")
  2381.             Block := [], ParentIndent := 0
  2382.        
  2383.         if Block.MinIndex()
  2384.             Current := Block, Cur := 1
  2385.         else
  2386.             Current := Lock, Cur := 0
  2387.        
  2388.         ; Round converts "" to 0
  2389.         Braces := Round(Current[Current.MaxIndex()].Braces)
  2390.         ParentIndent := Round(ParentIndentObj[Cur])
  2391.        
  2392.         if (First == "}")
  2393.         {
  2394.             while ((Found := SubStr(Text, A_Index, 1)) ~= "}|\s")
  2395.             {
  2396.                 if (Found ~= "\s")
  2397.                     continue
  2398.                 if (Cur && Current.MaxIndex() <= 1)
  2399.                     break
  2400.                 Special := Current.Pop().Ind, Braces--
  2401.             }
  2402.         }
  2403.        
  2404.         if (First == "{" && ParentIndent)
  2405.             ParentIndent--
  2406.        
  2407.         Out .= Newline
  2408.         Loop, % Special ? Special-1 : Round(Current[Current.MaxIndex()].Ind) + Round(ParentIndent)
  2409.             Out .= Indent
  2410.         Out .= Trim(Line)
  2411.        
  2412.         if (FirstTwo == "/*")
  2413.         {
  2414.             if (!Block.MinIndex())
  2415.             {
  2416.                 Block.Push({ParentIndent: ParentIndent
  2417.                 , Ind: Round(Lock[Lock.MaxIndex()].Ind) + 1
  2418.                 , Braces: Round(Lock[Lock.MaxIndex()].Braces) + 1})
  2419.             }
  2420.             Current := Block, ParentIndent := 0
  2421.         }
  2422.        
  2423.         if (Last == "{")
  2424.         {
  2425.             Braces++, ParentIndent := (IsExpCont && Last == "{") ? ParentIndent-1 : ParentIndent
  2426.             Current.Push({Braces: Braces
  2427.             , Ind: ParentIndent + Round(Current[Current.MaxIndex()].ParentIndent) + Braces
  2428.             , ParentIndent: ParentIndent + Round(Current[Current.MaxIndex()].ParentIndent)})
  2429.             ParentIndent := 0
  2430.         }
  2431.        
  2432.         if ((ParentIndent || IsExpCont || IndentCheck) && (IndentCheck && Last != "{"))
  2433.             ParentIndent++
  2434.         if (ParentIndent > 0 && !(IsExpCont || IndentCheck))
  2435.             ParentIndent := 0
  2436.        
  2437.         ParentIndentObj[Cur] := ParentIndent
  2438.         Special := 0
  2439.     }
  2440.    
  2441.     if Braces
  2442.         throw Exception("Segment Open!")
  2443.    
  2444.     return SubStr(Out, StrLen(Newline)+1)
  2445. }
  2446. ; Modified from https://github.com/cocobelgica/AutoHotkey-Util/blob/master/ExecScript.ahk
  2447. ExecScript(Script, Params="", AhkPath="")
  2448. {
  2449.     static Shell := ComObjCreate("WScript.Shell")
  2450.     Name := "\\.\pipe\AHK_CQT_" A_TickCount
  2451.     Pipe := []
  2452.     Loop, 3
  2453.     {
  2454.         Pipe[A_Index] := DllCall("CreateNamedPipe"
  2455.         , "Str", Name
  2456.         , "UInt", 2, "UInt", 0
  2457.         , "UInt", 255, "UInt", 0
  2458.         , "UInt", 0, "UPtr", 0
  2459.         , "UPtr", 0, "UPtr")
  2460.     }
  2461.     if !FileExist(AhkPath)
  2462.         throw Exception("AutoHotkey runtime not found: " AhkPath)
  2463.     if (A_IsCompiled && AhkPath == A_ScriptFullPath)
  2464.         AhkPath .= " /E"
  2465.     if FileExist(Name)
  2466.     {
  2467.         Exec := Shell.Exec(AhkPath " /CP65001 " Name " " Params)
  2468.         DllCall("ConnectNamedPipe", "UPtr", Pipe[2], "UPtr", 0)
  2469.         DllCall("ConnectNamedPipe", "UPtr", Pipe[3], "UPtr", 0)
  2470.         FileOpen(Pipe[3], "h", "UTF-8").Write(Script)
  2471.     }
  2472.     else ; Running under WINE with improperly implemented pipes
  2473.     {
  2474.         FileOpen(Name := "AHK_CQT_TMP.ahk", "w").Write(Script)
  2475.         Exec := Shell.Exec(AhkPath " /CP65001 " Name " " Params)
  2476.     }
  2477.     Loop, 3
  2478.         DllCall("CloseHandle", "UPtr", Pipe[A_Index])
  2479.     return Exec
  2480. }
  2481.  
  2482. DeHashBang(Script)
  2483. {
  2484.     AhkPath := A_AhkPath
  2485.     if RegExMatch(Script, "`a)^\s*`;#!\s*(.+)", Match)
  2486.     {
  2487.         AhkPath := Trim(Match1)
  2488.         Vars := {"%A_ScriptDir%": A_WorkingDir
  2489.         , "%A_WorkingDir%": A_WorkingDir
  2490.         , "%A_AppData%": A_AppData
  2491.         , "%A_AppDataCommon%": A_AppDataCommon
  2492.         , "%A_LineFile%": A_ScriptFullPath
  2493.         , "%A_AhkPath%": A_AhkPath
  2494.         , "%A_AhkDir%": A_AhkPath "\.."}
  2495.         for SearchText, Replacement in Vars
  2496.             StringReplace, AhkPath, AhkPath, %SearchText%, %Replacement%, All
  2497.     }
  2498.     return AhkPath
  2499. }
  2500.  
  2501. UrlDownloadToVar(Url)
  2502. {
  2503.     xhr := ComObjCreate("MSXML2.XMLHTTP")
  2504.     xhr.Open("GET", url, false), xhr.Send()
  2505.     return xhr.ResponseText
  2506. }
  2507.  
  2508. ; Helper function, to make passing in expressions resulting in function objects easier
  2509. SetTimer(Label, Period)
  2510. {
  2511.     SetTimer, %Label%, %Period%
  2512. }
  2513.  
  2514. SendMessage(Msg, wParam, lParam, hWnd)
  2515. {
  2516.     ; DllCall("SendMessage", "UPtr", hWnd, "UInt", Msg, "UPtr", wParam, "Ptr", lParam, "UPtr")
  2517.     SendMessage, Msg, wParam, lParam,, ahk_id %hWnd%
  2518.     return ErrorLevel
  2519. }
  2520.  
  2521. PostMessage(Msg, wParam, lParam, hWnd)
  2522. {
  2523.     PostMessage, Msg, wParam, lParam,, ahk_id %hWnd%
  2524.     return ErrorLevel
  2525. }
  2526.  
  2527. Ahkbin(Content, Name="", Desc="", Channel="")
  2528. {
  2529.     static URL := "https://p.ahkscript.org/"
  2530.     Form := "code=" UriEncode(Content)
  2531.     if Name
  2532.         Form .= "&name=" UriEncode(Name)
  2533.     if Desc
  2534.         Form .= "&desc=" UriEncode(Desc)
  2535.     if Channel
  2536.         Form .= "&announce=on&channel=" UriEncode(Channel)
  2537.    
  2538.     Pbin := ComObjCreate("MSXML2.XMLHTTP")
  2539.     Pbin.Open("POST", URL, False)
  2540.     Pbin.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
  2541.     Pbin.Send(Form)
  2542.     return Pbin.getResponseHeader("ahk-location")
  2543. }
  2544.  
  2545. ; Modified by GeekDude from http://goo.gl/0a0iJq
  2546. UriEncode(Uri, RE="[0-9A-Za-z]") {
  2547.     VarSetCapacity(Var, StrPut(Uri, "UTF-8"), 0), StrPut(Uri, &Var, "UTF-8")
  2548.     While Code := NumGet(Var, A_Index - 1, "UChar")
  2549.         Res .= (Chr:=Chr(Code)) ~= RE ? Chr : Format("%{:02X}", Code)
  2550.     Return, Res
  2551. }
  2552.  
  2553. CreateMenus(Menu)
  2554. {
  2555.     static MenuName := 0
  2556.     Menus := ["Menu_" MenuName++]
  2557.     for each, Item in Menu
  2558.     {
  2559.         Ref := Item[2]
  2560.         if IsObject(Ref) && Ref._NewEnum()
  2561.         {
  2562.             SubMenus := CreateMenus(Ref)
  2563.             Menus.Push(SubMenus*), Ref := ":" SubMenus[1]
  2564.         }
  2565.         Menu, % Menus[1], Add, % Item[1], %Ref%
  2566.     }
  2567.     return Menus
  2568. }
  2569.  
  2570. Ini_Load(Contents)
  2571. {
  2572.     Section := Out := []
  2573.     loop, Parse, Contents, `n, `r
  2574.     {
  2575.         if ((Line := Trim(A_LoopField)) ~= "^;|^$")
  2576.             continue
  2577.         else if RegExMatch(Line, "^\[(.+)\]$", Match)
  2578.             Out[Match1] := (Section := [])
  2579.         else if RegExMatch(Line, "^(.+?)=(.*)$", Match)
  2580.             Section[Trim(Match1)] := Trim(Match2)
  2581.     }
  2582.     return Out
  2583. }
  2584.  
  2585. GetFullPathName(FilePath)
  2586. {
  2587.     VarSetCapacity(Path, A_IsUnicode ? 520 : 260, 0)
  2588.     DllCall("GetFullPathName", "Str", FilePath
  2589.     , "UInt", 260, "Str", Path, "Ptr", 0, "UInt")
  2590.     return Path
  2591. }
  2592.  
  2593. RichEdit_AddMargins(hRichEdit, x:=0, y:=0, w:=0, h:=0)
  2594. {
  2595.     static WineVer := DllCall("ntdll.dll\wine_get_version", "AStr")
  2596.     VarSetCapacity(RECT, 16, 0)
  2597.     if (x | y | w | h)
  2598.     {
  2599.         if WineVer
  2600.         {
  2601.             ; Workaround for bug in Wine 3.0.2.
  2602.             ; This code will need to be updated this code
  2603.             ; after future Wine releases that fix it.
  2604.             NumPut(x, RECT,  0, "Int"), NumPut(y, RECT,  4, "Int")
  2605.             NumPut(w, RECT,  8, "Int"), NumPut(h, RECT, 12, "Int")
  2606.         }
  2607.         else
  2608.         {
  2609.             if !DllCall("GetClientRect", "UPtr", hRichEdit, "UPtr", &RECT, "UInt")
  2610.                 throw Exception("Couldn't get RichEdit Client RECT")
  2611.             NumPut(x + NumGet(RECT,  0, "Int"), RECT,  0, "Int")
  2612.             NumPut(y + NumGet(RECT,  4, "Int"), RECT,  4, "Int")
  2613.             NumPut(w + NumGet(RECT,  8, "Int"), RECT,  8, "Int")
  2614.             NumPut(h + NumGet(RECT, 12, "Int"), RECT, 12, "Int")
  2615.         }
  2616.     }
  2617.     SendMessage(0xB3, 0, &RECT, hRichEdit)
  2618. }
  2619.  
  2620. ;
  2621. ; Based on code from fincs' Ahk2Exe - https://github.com/fincs/ahk2exe
  2622. ;
  2623.  
  2624. PreprocessScript(ByRef ScriptText, AhkScript, KeepComments=1, KeepIndent=1, KeepEmpties=0, FileList="", FirstScriptDir="", Options="", iOption=0)
  2625. {
  2626.     SplitPath, AhkScript, ScriptName, ScriptDir
  2627.     if !IsObject(FileList)
  2628.     {
  2629.         FileList := [AhkScript]
  2630.         ; ScriptText := "; <COMPILER: v" A_AhkVersion ">`n"
  2631.         FirstScriptDir := A_WorkingDir
  2632.         IsFirstScript := true
  2633.         Options := { comm: ";", esc: "``" }
  2634.        
  2635.         OldWorkingDir := A_WorkingDir
  2636.         SetWorkingDir, %FirstScriptDir%
  2637.     }
  2638.    
  2639.     IfNotExist, %AhkScript%
  2640.         if !iOption
  2641.             Util_Error((IsFirstScript ? "Script" : "#include") " file """ AhkScript """ cannot be opened.")
  2642.     else return
  2643.        
  2644.     cmtBlock := false, contSection := false
  2645.     Loop, Read, %AhkScript%
  2646.     {
  2647.         tline := Trim(A_LoopReadLine)
  2648.         RegExMatch(A_LoopReadLine, "^[ \t]+", indent)
  2649.         if !cmtBlock
  2650.         {
  2651.             if !contSection
  2652.             {
  2653.                 if StrStartsWith(tline, Options.comm) && !KeepComments
  2654.                     continue
  2655.                 else if (tline = "" && !KeepEmpties)
  2656.                     continue
  2657.                 else if StrStartsWith(tline, "/*")
  2658.                 {
  2659.                     if KeepComments
  2660.                         ScriptText .= A_LoopReadLine "`n"
  2661.                     cmtBlock := true
  2662.                     continue
  2663.                 }
  2664.             }
  2665.             if StrStartsWith(tline, "(") && !IsFakeCSOpening(tline)
  2666.                 contSection := true
  2667.             else if StrStartsWith(tline, ")")
  2668.                 contSection := false
  2669.            
  2670.             ttline := RegExReplace(tline, "\s+" RegExEscape(Options.comm) ".*$", "")
  2671.             if !contSection && RegExMatch(ttline, "i)^#Include(Again)?[ \t]*[, \t]?\s+(.*)$", o)
  2672.             {
  2673.                 IsIncludeAgain := (o1 = "Again")
  2674.                 IgnoreErrors := false
  2675.                 IncludeFile := o2
  2676.                 if RegExMatch(IncludeFile, "\*[iI]\s+?(.*)", o)
  2677.                     IgnoreErrors := true, IncludeFile := Trim(o1)
  2678.                
  2679.                 if RegExMatch(IncludeFile, "^<(.+)>$", o)
  2680.                 {
  2681.                     if IncFile2 := FindLibraryFile(o1, FirstScriptDir)
  2682.                     {
  2683.                         IncludeFile := IncFile2
  2684.                         goto _skip_findfile
  2685.                     }
  2686.                 }
  2687.                
  2688.                 StringReplace, IncludeFile, IncludeFile, `%A_ScriptDir`%, %FirstScriptDir%, All
  2689.                 StringReplace, IncludeFile, IncludeFile, `%A_AppData`%, %A_AppData%, All
  2690.                 StringReplace, IncludeFile, IncludeFile, `%A_AppDataCommon`%, %A_AppDataCommon%, All
  2691.                 StringReplace, IncludeFile, IncludeFile, `%A_LineFile`%, %AhkScript%, All
  2692.                
  2693.                 if InStr(FileExist(IncludeFile), "D")
  2694.                 {
  2695.                     SetWorkingDir, %IncludeFile%
  2696.                     continue
  2697.                 }
  2698.                
  2699.                 _skip_findfile:
  2700.                
  2701.                 IncludeFile := Util_GetFullPath(IncludeFile)
  2702.                
  2703.                 AlreadyIncluded := false
  2704.                 for k,v in FileList
  2705.                     if (v = IncludeFile)
  2706.                     {
  2707.                         AlreadyIncluded := true
  2708.                         break
  2709.                     }
  2710.                 if(IsIncludeAgain || !AlreadyIncluded)
  2711.                 {
  2712.                     if !AlreadyIncluded
  2713.                         FileList.Insert(IncludeFile)
  2714.                     PreprocessScript(ScriptText, IncludeFile, KeepComments, KeepIndent, KeepEmpties, FileList, FirstScriptDir, Options, IgnoreErrors)
  2715.                 }
  2716.             }else if !contSection && ttline ~= "i)^FileInstall[, \t]"
  2717.             {
  2718.                 if ttline ~= "^\w+\s+(:=|\+=|-=|\*=|/=|//=|\.=|\|=|&=|\^=|>>=|<<=)"
  2719.                     continue ; This is an assignment!
  2720.                
  2721.                 ; workaround for `, detection
  2722.                 EscapeChar := Options.esc
  2723.                 EscapeCharChar := EscapeChar EscapeChar
  2724.                 EscapeComma := EscapeChar ","
  2725.                 EscapeTmp := chr(2)
  2726.                 EscapeTmpD := chr(3)
  2727.                 StringReplace, ttline, ttline, %EscapeCharChar%, %EscapeTmpD%, All
  2728.                 StringReplace, ttline, ttline, %EscapeComma%, %EscapeTmp%, All
  2729.                
  2730.                 if !RegExMatch(ttline, "i)^FileInstall[ \t]*[, \t][ \t]*([^,]+?)[ \t]*(,|$)", o) || o1 ~= "[^``]%"
  2731.                     Util_Error("Error: Invalid ""FileInstall"" syntax found. Note that the first parameter must not be specified using a continuation section.")
  2732.                 _ := Options.esc
  2733.                 StringReplace, o1, o1, %_%`%, `%, All
  2734.                 StringReplace, o1, o1, %_%`,, `,, All
  2735.                 StringReplace, o1, o1, %_%%_%,, %_%,, All
  2736.                
  2737.                 ; workaround for `, detection [END]
  2738.                 StringReplace, o1, o1, %EscapeTmp%, `,, All
  2739.                 StringReplace, o1, o1, %EscapeTmpD%, %EscapeChar%, All
  2740.                 StringReplace, ttline, ttline, %EscapeTmp%, %EscapeComma%, All
  2741.                 StringReplace, ttline, ttline, %EscapeTmpD%, %EscapeCharChar%, All
  2742.                
  2743.                 ScriptText .= (KeepIndent ? indent : "") (KeepComments ? tline : ttline) "`n"
  2744.             }else if !contSection && RegExMatch(tline, "i)^#CommentFlag\s+(.+)$", o)
  2745.                 Options.comm := o1, ScriptText .= (KeepIndent ? indent : "") (KeepComments ? tline : ttline) "`n"
  2746.             else if !contSection && RegExMatch(tline, "i)^#EscapeChar\s+(.+)$", o)
  2747.                 Options.esc := o1, ScriptText .= (KeepIndent ? indent : "") (KeepComments ? tline : ttline) "`n"
  2748.             else if !contSection && RegExMatch(tline, "i)^#DerefChar\s+(.+)$", o)
  2749.                 Util_Error("Error: #DerefChar is not supported.")
  2750.             else if !contSection && RegExMatch(tline, "i)^#Delimiter\s+(.+)$", o)
  2751.                 Util_Error("Error: #Delimiter is not supported.")
  2752.             else
  2753.                 ScriptText .= (contSection ? A_LoopReadLine : (KeepIndent ? indent : "") (KeepComments ? tline : ttline)) "`n"
  2754.         }else{
  2755.             if KeepComments
  2756.                 ScriptText .= A_LoopReadLine "`n"
  2757.             if StrStartsWith(tline, "*/")
  2758.                 cmtBlock := false
  2759.         }
  2760.     }
  2761.    
  2762.     Loop, % !!IsFirstScript ; equivalent to "if IsFirstScript" except you can break from the block
  2763.     {
  2764.         static AhkPath := A_IsCompiled ? A_ScriptDir "\..\AutoHotkey.exe" : A_AhkPath
  2765.         IfNotExist, %AhkPath%
  2766.             break ; Don't bother with auto-includes because the file does not exist
  2767.        
  2768.         ; Auto-including any functions called from a library...
  2769.         ilibfile = %A_Temp%\_ilib.ahk
  2770.         IfExist, %ilibfile%, FileDelete, %ilibfile%
  2771.             AhkType := AHKType(AhkPath)
  2772.         if AhkType = FAIL
  2773.             Util_Error("Error: The AutoHotkey build used for auto-inclusion of library functions is not recognized.", 1, AhkPath)
  2774.         if AhkType = Legacy
  2775.             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)
  2776.         tmpErrorLog := Util_TempFile()
  2777.         RunWait, "%AhkPath%" /iLib "%ilibfile%" /ErrorStdOut "%AhkScript%" 2>"%tmpErrorLog%", %FirstScriptDir%, UseErrorLevel
  2778.         FileRead,tmpErrorData,%tmpErrorLog%
  2779.         FileDelete,%tmpErrorLog%
  2780.         if (ErrorLevel = 2)
  2781.             Util_Error("Error: The script contains syntax errors.",1,tmpErrorData)
  2782.         IfExist, %ilibfile%
  2783.         {
  2784.             PreprocessScript(ScriptText, ilibfile, KeepComments, KeepIndent, KeepEmpties, FileList, FirstScriptDir, Options)
  2785.             FileDelete, %ilibfile%
  2786.         }
  2787.         StringTrimRight, ScriptText, ScriptText, 1 ; remove trailing newline
  2788.     }
  2789.    
  2790.     if OldWorkingDir
  2791.         SetWorkingDir, %OldWorkingDir%
  2792. }
  2793.  
  2794. IsFakeCSOpening(tline)
  2795. {
  2796.     Loop, Parse, tline, %A_Space%%A_Tab%
  2797.         if !StrStartsWith(A_LoopField, "Join") && InStr(A_LoopField, ")")
  2798.             return true
  2799.     return false
  2800. }
  2801.  
  2802. FindLibraryFile(name, ScriptDir)
  2803. {
  2804.     libs := [ScriptDir "\Lib", A_MyDocuments "\AutoHotkey\Lib", A_ScriptDir "\..\Lib"]
  2805.     p := InStr(name, "_")
  2806.     if p
  2807.         name_lib := SubStr(name, 1, p-1)
  2808.    
  2809.     for each,lib in libs
  2810.     {
  2811.         file := lib "\" name ".ahk"
  2812.         IfExist, %file%
  2813.             return file
  2814.        
  2815.         if !p
  2816.             continue
  2817.        
  2818.         file := lib "\" name_lib ".ahk"
  2819.         IfExist, %file%
  2820.             return file
  2821.     }
  2822. }
  2823.  
  2824. StrStartsWith(ByRef v, ByRef w)
  2825. {
  2826.     return SubStr(v, 1, StrLen(w)) = w
  2827. }
  2828.  
  2829. RegExEscape(String)
  2830. {
  2831.     return "\Q" StrReplace(String, "\E", "\E\\E\Q") "\E"
  2832. }
  2833.  
  2834. Util_TempFile(d:="")
  2835. {
  2836.     if ( !StrLen(d) || !FileExist(d) )
  2837.         d:=A_Temp
  2838.     Loop
  2839.         tempName := d "\~temp" A_TickCount ".tmp"
  2840.     until !FileExist(tempName)
  2841.     return tempName
  2842. }
  2843.  
  2844. Util_GetFullPath(path)
  2845. {
  2846.     VarSetCapacity(fullpath, 260 * (!!A_IsUnicode + 1))
  2847.     if DllCall("GetFullPathName", "str", path, "uint", 260, "str", fullpath, "ptr", 0, "uint")
  2848.         return fullpath
  2849.     else
  2850.         return ""
  2851. }
  2852.  
  2853. Util_Error(txt, doexit=1, extra="")
  2854. {
  2855.     throw Exception(txt, -2, extra)
  2856. }
  2857.  
  2858. ; Based on code from SciTEDebug.ahk
  2859. AHKType(exeName)
  2860. {
  2861.     FileGetVersion, vert, %exeName%
  2862.     if !vert
  2863.         return "FAIL"
  2864.    
  2865.     StringSplit, vert, vert, .
  2866.     vert := vert4 | (vert3 << 8) | (vert2 << 16) | (vert1 << 24)
  2867.    
  2868.     exeMachine := GetExeMachine(exeName)
  2869.     if !exeMachine
  2870.         return "FAIL"
  2871.    
  2872.     if (exeMachine != 0x014C) && (exeMachine != 0x8664)
  2873.         return "FAIL"
  2874.    
  2875.     if !(VersionInfoSize := DllCall("version\GetFileVersionInfoSize", "str", exeName, "uint*", null, "uint"))
  2876.         return "FAIL"
  2877.    
  2878.     VarSetCapacity(VersionInfo, VersionInfoSize)
  2879.     if !DllCall("version\GetFileVersionInfo", "str", exeName, "uint", 0, "uint", VersionInfoSize, "ptr", &VersionInfo)
  2880.         return "FAIL"
  2881.    
  2882.     if !DllCall("version\VerQueryValue", "ptr", &VersionInfo, "str", "\VarFileInfo\Translation", "ptr*", lpTranslate, "uint*", cbTranslate)
  2883.         return "FAIL"
  2884.    
  2885.     oldFmt := A_FormatInteger
  2886.     SetFormat, IntegerFast, H
  2887.     wLanguage := NumGet(lpTranslate+0, "UShort")
  2888.     wCodePage := NumGet(lpTranslate+2, "UShort")
  2889.     id := SubStr("0000" SubStr(wLanguage, 3), -3, 4) SubStr("0000" SubStr(wCodePage, 3), -3, 4)
  2890.     SetFormat, IntegerFast, %oldFmt%
  2891.    
  2892.     if !DllCall("version\VerQueryValue", "ptr", &VersionInfo, "str", "\StringFileInfo\" id "\ProductName", "ptr*", pField, "uint*", cbField)
  2893.         return "FAIL"
  2894.    
  2895.     ; Check it is actually an AutoHotkey executable
  2896.     if !InStr(StrGet(pField, cbField), "AutoHotkey")
  2897.         return "FAIL"
  2898.    
  2899.     ; We're dealing with a legacy version if it's prior to v1.1
  2900.     return vert >= 0x01010000 ? "Modern" : "Legacy"
  2901. }
  2902.  
  2903. GetExeMachine(exepath)
  2904. {
  2905.     if !(exe := FileOpen(exepath, "r"))
  2906.         return
  2907.    
  2908.     exe.Seek(60), exe.Seek(exe.ReadUInt()+4)
  2909.     return exe.ReadUShort()
  2910. }
  2911. class HelpFile
  2912. {
  2913.     static BaseURL := "ms-its:" A_AhkPath "\..\AutoHotkey.chm::/docs/"
  2914.     static Cache := {"Syntax": {}}
  2915.    
  2916.     GetPage(Path)
  2917.     {
  2918.         static xhttp := ComObjCreate("MSXML2.XMLHTTP.3.0")
  2919.         html := ComObjCreate("htmlfile")
  2920.         Path := this.BaseURL . RegExReplace(Path, "[?#].+")
  2921.         xhttp.open("GET", Path, True), xhttp.send()
  2922.         html.open(), html.write(xhttp.responseText), html.close()
  2923.         while !(html.readyState = "interactive" || html.readyState = "complete")
  2924.             Sleep, 50
  2925.         return html
  2926.     }
  2927.    
  2928.     GetLookup()
  2929.     {
  2930.         if this.Lookup
  2931.             return this.Lookup
  2932.        
  2933.         ; Scrape the command reference
  2934.         this.Commands := {}
  2935.         try
  2936.             Page := this.GetPage("commands/index.htm")
  2937.        
  2938.         try ; Windows
  2939.             rows := Page.querySelectorAll(".info td:first-child a")
  2940.         catch ; Wine
  2941.             try
  2942.                 rows := Page.body.querySelectorAll(".info td:first-child a")
  2943.         catch ; IE8
  2944.         {
  2945.             rows := new this.HTMLCollection()
  2946.             trows := Page.getElementsByTagName("table")[0].children[0].children
  2947.             loop, % trows.length
  2948.                 rows.push(trows.Item(A_Index-1).children[0].children[0])
  2949.         }
  2950.        
  2951.         loop, % rows.length
  2952.             for i, text in StrSplit((row := rows.Item(A_Index-1)).innerText, "/")
  2953.                 if RegExMatch(text, "^[\w#]+", Match) && !this.Commands.HasKey(Match)
  2954.                     this.Commands[Match] := "commands/" RegExReplace(row.getAttribute("href"), "^about:")
  2955.        
  2956.         ; Scrape the variables page
  2957.         this.Variables := {}
  2958.         try
  2959.             Page := this.GetPage("Variables.htm")
  2960.        
  2961.         try ; Windows
  2962.             rows := Page.querySelectorAll(".info td:first-child")
  2963.         catch ; Wine
  2964.             try
  2965.                 rows := Page.body.querySelectorAll(".info td:first-child")
  2966.         catch ; IE8
  2967.         {
  2968.             rows := new this.HTMLCollection()
  2969.             tables := Page.getElementsByTagName("table")
  2970.             loop, % tables.length
  2971.             {
  2972.                 trows := tables.Item(A_Index-1).children[0].children
  2973.                 loop, % trows.length
  2974.                     rows.push(trows.Item(A_Index-1).children[0])
  2975.             }
  2976.         }
  2977.        
  2978.         loop, % rows.length
  2979.             if RegExMatch((row := rows.Item(A_Index-1)).innerText, "(A_\w+)", Match)
  2980.                 this.Variables[Match1] := "Variables.htm#" row.parentNode.getAttribute("id")
  2981.        
  2982.         ; Combine
  2983.         this.Lookup := this.Commands.Clone()
  2984.         for k, v in this.Variables
  2985.             this.Lookup[k] := v
  2986.        
  2987.         return this.Lookup
  2988.     }
  2989.    
  2990.     Open(Keyword:="")
  2991.     {
  2992.         Lookup := this.GetLookup()
  2993.         Suffix := Lookup[Keyword] ? Lookup[Keyword] : "AutoHotkey.htm"
  2994.         Run, % "hh.exe """ this.BaseURL . Suffix """"
  2995.     }
  2996.    
  2997.     GetSyntax(Keyword:="")
  2998.     {
  2999.         ; Generate this.Commands
  3000.         this.GetLookup()
  3001.        
  3002.         ; Only look for Syntax of commands
  3003.         if !(Path := this.Commands[Keyword])
  3004.             return
  3005.        
  3006.         ; Try to find it in the cache
  3007.         if this.Cache.Syntax.HasKey(Keyword)
  3008.             return this.Cache.Syntax[Keyword]
  3009.        
  3010.         ; Get the right DOM to search
  3011.         Page := this.GetPage(Path)
  3012.         Root := Page ; Keep the page root in memory or it will be garbage collected
  3013.         if RegExMatch(Path, "#\K.+", ID)
  3014.             Page := Page.getElementById(ID)
  3015.        
  3016.         try ; Windows
  3017.             Nodes := page.getElementsByClassName("Syntax")
  3018.         catch ; Wine
  3019.             try
  3020.                 Nodes := page.body.getElementsByClassName("Syntax")
  3021.         catch ; IE8
  3022.             Nodes := page.getElementsByTagName("pre")
  3023.        
  3024.         try ; Windows
  3025.             Text := Nodes.Item(0).innerText
  3026.         catch ; Some versions of Wine
  3027.             Text := Nodes.Item(0).innerHTML
  3028.        
  3029.         ; Cache and return the result
  3030.         this.Cache.Syntax[Keyword] := StrSplit(Text, "`n", "`r")[1]
  3031.         return this.Cache.Syntax[Keyword]
  3032.     }
  3033.    
  3034.     class HTMLCollection
  3035.     {
  3036.         length[]
  3037.         {
  3038.             get
  3039.             {
  3040.                 ; Rounding MaxIndex produces a similar effect
  3041.                 ; to this.Length(), but doesn't trigger recursion
  3042.                 return Round(this.MaxIndex())
  3043.             }
  3044.         }
  3045.        
  3046.         Item(i)
  3047.         {
  3048.             return this[i+1]
  3049.         }
  3050.     }
  3051. }
  3052.  
  3053. #R::Reload
  3054. #S::Suspend
  3055. #P::Pause
  3056. ESC::ExitApp
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement