TillaGoto - Go to functions, labels & hks in your script
The cool thing is that a lot of things are done programmatically through the Scintilla APIs. Thanks to that, the script can be scanned on demand without the user noticing. As well, it becomes trivial to go to a specific line programmatically.
Note: Only works with Scintilla editors. The ones I know of are Notepad++, Notepad2, SciTE and SciTE4AutoHotkey.
The settings are preconfigured for those editors, but you just have to change sActiveWindow to the title of your editor's window to make it work with other Scintilla editors.
Important note for SciTE/SciTE4AutoHotkey users: the values of check.if.already.open and title.full.path (found in the file SciTEGlobal.properties) must be set to 1 if you wish to use the #Include file scanning feature. In SciTE4AutoHotkey, that is the default value. But not in SciTE.
Important note for Notepad2 users: the setting Window Title Display must be set to Full Pathname. This setting can be found in the Settings menu.
Features:
[*:oe94ynni] Simply middle click a function/label to go to its definition!
[*:oe94ynni] Capable of listing and opening functions in #Include files and libraries
[*:oe94ynni] Go back and forth between the lines you jumped to using the mouse or keyboard
[*:oe94ynni] Compatible with all AutoHotkey versions (ANSI/Unicode & 32/64-bit).
[*:oe94ynni] Instant refresh. The script is scanned on call.
[*:oe94ynni] Incremental search
[*:oe94ynni] Match-anywhere searches also available (see script)
[*:oe94ynni] Cool fade-in effect!
[*:oe94ynni] Custom transparency
[*:oe94ynni] Customizable hotkey (default is F1 - see script to change)
[*:oe94ynni] Customizable appearance (width and height)
[*:oe94ynni] Works with any editor that uses Scintilla (eg. Notepad++, SciTE, SciTE4AutoHotkey)
[*:oe94ynni] Seemless integration with the editor (programmatically scans the script and goes to specified line)
[*:oe94ynni] Works with any bracing style (OTB or not).
[*:oe94ynni] Selecting a function name or label and pressing the hotkey will automatically bring you to the definition line. In fact, selecting any text which would yield only one answer in the incremental search will automatically redirect you to the matching line upon hotkey press.Usage:
[*:oe94ynni] You can customize the appearance of the GUI by changing the values of iGUIWidth (default is 230), iGUIHeight (default is 12 rows), iMargin (default is 2), bPosLeft (default is False), bWideView (default is True), iTransparency (default is 255), bTrayIcon (default is True), iAlignFilenames (default is False), iControlFontSize (default is 8), and fControlFont (default is Courier New). See the script for more details.
[*:oe94ynni] If the default colours don't match your lexing/theme, you may change the background of the GUI and the controls and the text colour by changing the values of cGUIBG, cControlBG, and cControlFG.
[*:oe94ynni] For extremely large scripts (> 5000 lines, especially with a lot of comments), performance may be enhanced by turning comment filtering off through bFilterComments (default is True). On the same note, you may turn the flag off anyways to improve performance if you're confident your comments do not contain function/label/hotkey-looking text that might get picked up during script analysis.
[*:oe94ynni] Because large blocks of comments are the most intensive to scan, if you know a comment block does not contain labels/hotkeys/functions look-alikes, you can exclude it from comment filtering by adding an exclamation mark (ie. "!") in the starting line of the block (ie. "/*!")
[*:oe94ynni] The variable iIncludeMode (default is 0x10100101) allows you to customize how TillaGoto should scan #Include files and library functions. See the script for the full list of options available. Note that if this feature is used, the variable sPathMatching should be properly set for TillaGoto to extract the path from the window title (default is set for Notepad++ and SciTE4AutoHotkey). Using the #Include file scanning feature reduces performance and should be turned off (or at least comment filtering) if lag is experienced. If you use SciTE/SciTE4AutoHotkey or Notepad2, see the important note above for the #Include file scanning feature to work properly.
[*:oe94ynni] You can change the behaviour of TillaGoto on a per script basis by using directives. See the bDirectives setting for more details.
[*:oe94ynni] If using the #Include file scanning feature, it is strongly recommended to enable file caching, by setting bCacheFiles to True (default value). This will allow TillaGoto to be much faster on subsequent scans of the same include files.Using the keyboard[*:oe94ynni] You can use TillaGoto in two ways, depending on the value of bQuickMode. When bQuickMode is False (default), TillaGoto will monitor the active window for the editor you specified in sActiveWindow and open the GUI everytime you press the hotkey specified by uHotkey. If bQuickMode is True, running the script will automatically open the GUI, assuming the active window upon execution matches sActiveWindow.
[*:oe94ynni] Pressing the hotkey again while the GUI is open will either:[*:oe94ynni] go to the selected item, [*:oe94ynni] or, if the GUI doesn't have focus, put focus back on the search textbox (and update it with any text that was selected).[*:oe94ynni] When the GUI is open, you can type in the TextBox in order to narrow down your search by filtering the list of functions/labels/hotkeys. If bMatchEverywhere is False, typing anything in the TextBox will filter out all the functions/labels/hotkeys that do not begin with the text you typed. If bMatchEverywhere is True (default), typing in the TextBox will filter out all the functions/labels/hotkeys that do not contain all the words you typed. For example, typing "() func" will filter out all the functions/labels/hotkeys that do not contain the strings "()" and "func". You can also exclude items containing a certain string by preceding the string with an exclamation mark (ie. "!"). For example, typing "!() MyLabel" will filter out all the functions/labels/hotkeys that do not contain "MyLabel" as well as those that do contain "()".
[*:oe94ynni] You can go back and forth between the points you recently jumped to by pressing the hotkeys specified in uGoBack and uGoForward (default is Alt+Left and Alt+Right)
[*:oe94ynni] You can go to the definition line of the function/label on which the caret is located by pressing the hotkeys specified in uGotoDef (default is Shift+Enter). It is the keyboard equivalent of middleclicking on a function/label name.[/list]Using the mouse[*:oe94ynni] If bUseMButton is True (it is by default), you can use the middle mouse button to open the GUI, as well as to go to a function/label definition line simply by middle-clicking on its name.
[*:oe94ynni] Upon opening the GUI (or anytime the TextBox has focus), you can move the selection by using the mouse wheel. After selecting the function you want, simply click the middle mouse button or press the hotkey again to go to the selected item. This is useful for when you see the function in the list. You can then quickly scroll to it.
[*:oe94ynni] If you'd like to close the GUI using the mouse, you can depress the middle mouse button for the time duration specified in iCancelWait (default is 300 ms).
[*:oe94ynni] You can go back and forth between the points you recently jumped to by doing Shift + Mouse wheelThanks to majkinetor for his Remote Buffer library, which helped me understand how to use those functions in TillaGoto.
This script also uses heresy's EmptyMem() function, included in the script (thanks heresy!)
This script also uses a function based on Lazslo's code (which itself is based on Shimanov's code) found here.
And finally, it uses code largely based on Lazslo's CRC32() and MCode() functions.
Download
Requires AutoHotkey version 1.0.48+
Suggestions, comments always welcome! :D
Changelog
December 30, 2010
- Added support for 64-bit and Unicode AHK
- Large performance increase by tweaking comment filtering and regexes
- Added library file support for ScanFile directive
- Added user library scanning
- Removed the dependency on RemoteBuf (functions have been inlined)
- Added iAlignFilenames (and removed bAlignFilenames which was unreliable)
- Improved reliability of middle-clicking on functions/labels
- Improved reliability of line history
- Improved caching system reliability and performance
October 23, 2010
- Fixed bugs with files only using CR or LF.
- Fixed small middle clicking bug.
- Fixed comment filtering bug.
- Minor improvements.
March 2, 2010
- Fixed a small bug causing middle clicking not to work if the GUI has never been summoned.
February 25, 2010
- Added sMustExist and bQuitWithEditor
- Added bAlignFilenames
- Fixed include directive scanning not following working directory changes
- Fixed #Include lines at the end of the script causing TillaGoto to stick in a loop
- Improved Include regex to support IncludeAgain and the *i option
- Improved label and hotkey scanning to include all valid labels and hotkeys
- Improved main loop and hotkey implementation
- Improved accuracy of various regexes
- Improved configuration documentation
- Improved bQuickMode implementation
- Improved filename alignment implementation
- Minor fixes and improvements
- Changed symbols when appending names of include files ("\") and library files ("|") because the previous symbol was too common in hotkeys
February 13, 2010
Major changes:
- Added ScanFile directive
- Added ability to differentiate identically named functions/labels/hotkeys from different #Include/library files
- Aligned appended filenames to the right
- TGcache files are deleted even if last instance didn't exit properly
- Fixed bug where TillaGoto would stop showing when called
- Fixed line history not working across multiple scripts
Minor changes:
- Improved bWideView implementation (and fixed hscrollbar showing up under certain conditions)
- Improved middle click implementation
- Changed hotkey focusing behaviour
- Different symbols when appending names of include files ("#") and library files ("\")
- Added bTrayIcon (default True)
- Added iControlFontSize (default 8)
- Added fControlFont (default Courier New)
- Changed default value of bCacheFiles to True
- Changed default value of iIncludeMode to 0x10100101 (scan include files for functions and append name)
- Minor fixes and improvements
May 4, 2009
- Added the ability to cache files (see Usage and script for details)
- Middleclicking will also move the caret to the position clicked before going to the clicked function/label/hotkey
- Simplified GUIInteract() and cleaned up HID stuff
April 6, 2009
- Added full support for Notepad2 (see important note above)
- Added cGUIBG, cControlBG, and cControlFG (see usage)
- The position of the GUI now takes into account the presence of a vertical scrollbar
- Simplified and improved compatibility of the external file launching routine
- Fixed End key and Home key being sent to the listbox instead of the textbox. Now, doing Ctrl+End and Ctrl+Home while focused on the textbox will send End and Home (respectively) to the listbox
- Fixed matching ending character in regexes to support files with no CR characters
Mar 29, 2009
- Fixed bug causing line history not to record current line when using the GUI.
- Fixed bug causing scripts that only use LF (instead of CRLF) as line delimiter wouldn't get scanned.
Mar 28, 2009
- Added bWideView (see script)
- Improved respect to iGUIHeight so that the number of items shown is always the same, even with a hscrollbar
Mar 22, 2009
- Added uGotoDef (see keyboard usage)
- Added a few tweaks to reduce listbox flickering during typing
- Changing selection now keeps focus on the textbox
- Simplified the directives system
- Fixed small bug in line history feature
Mar 20, 2009
- Added the ability to exclude words (see in keyboard usage)
- The directive TillaGoto.bFilterComments is now also valid for #Include files (it will override the default setting)
- Added the ability to exclude comment blocks from scanning (see in general usage)
- The line history feature now remembers the line history across different opened script (by comparing the script path). As a side-effect, resaving (ie. "Save As...") the same script as another file will erase the line history.
- sPathMatching is now mandatory (line history relies on it)
Mar 18, 2009
- Added bDirectives for TillaGoto directives
- Fixed sPathMatching failing to match for modified Notepad++ documents
- Changed LShift to Shift in general for line history feature
- Fixed bug where the next line of comments wouldn't get commented out
Mar 16, 2009
- Largely improved the Line History feature. It now behaves much more intuitively.
Mar 15, 2009
- Changed AbsolutePath() to use APIs (much more reliable)
Mar 14, 2009
- Fixed sPathMatching (it was missing a closing parenthesis)
- Fixed sEditorPath for Quick Mode (thanks HotKeyIt)
- Added the ability to middle-click on external functions and labels (thanks fincs)
- Fixed external file opening for labels and hotkeys (thanks HotKeyIt!)
- Added a line history feature allowing you to quickly go back and forth between recent jumps
- Fixed AbsolutePath() to be more accurate
- Fixed middleclick not working when appending name of file to external functions (thanks fincs!)
- Changed the external file launching procedure so that the requested line is at the top!
- Fixed library files being scanned for things other than functions
- Fixed the possibility of having the same file scanned multiple times
- Fixed file name being trimmed only if it ends with ".ahk" (thanks HotKeyIt)
Mar 13, 2009
- Added iIncludeMode and sPathMatching for #Include file scanning and library files scanning (and opening)
- Removed MsgBoxes in HID_GetInputInfo() which would rarely occur
- Fixed a possible error for when WM_INPUT's handle expired
- Fixed default value of sPathMatching and sActiveWindow so that it better matches SciTE/SciTE4AutoHotkey
Mar 08, 2009
- Added bFilterComments
- Added SetBatchLines -1 to the algorithm to make it faster
- Improved even more the comment filtering algorithm to make it even faster (noticeable difference for big scripts)
Mar 07, 2009
- Greatly improved analysis speed for large scripts with a lot of comments
- Added bPosLeft (feature suggested by HotKeyIt)
- Added iMargin
- Made the default size a little wider and taller
- Changed default value of sActiveWindow to match both Notepad++ and SciTE4AutoHotkey
- Added the SciTE4AutoHotkey fix by HotKeyIt
Mar 06, 2009
- Added bUseMButton (see usage and script for details)
- Added EmptyMem() by heresy to keep memory usage to a minimum
- Added iCancelWait (see usage and script for details)
Mar 05, 2009
- You can now click the middle mouse button for selection using the mouse wheel
- Fixed bug where commented hotkeys would show up in the list
Mar 04, 2009
- Added bQuickMode
- Changed location of GUI: it now shows up right beside the scrollbar
- Fixed script scanning so that line comments don't conflict with scanning (thanks HotKeyIt for reporting it!)
- Fixed bug where single-character hotkeys wouldn't show up (thanks HotKeyIt for reporting it!)
- Fixed possible endless loop if the hotkey is on the last line
- Added mouse wheel support for quick selection (see usage)
Mar 03, 2009
- Fixed a bug where If() and While() would be added (thanks fincs!)
- Added the ability to match anywhere, and match multiple words (see script)
- Made the GUI a little skinnier by reducing the margin
- GUI now closes upon selecting another window
- Added the ability to select transparency (see iTransparency in script)
- GUI now shows up in the corner of the window. Let me know what you think.
- Made the script 1024x768 friendly (ie. cut long lines in shorter ones)
- Added support for hotkey scanning as well!
- Restructured script to be more organized
- Added support for clone mode in Notepad++
- Made minor optimizations
Also, if you want to support SciTE(4AutoHotkey), use "ahk_class SciTEWindow", please
You can use whatever you want for sActiveWindow. It will be processed through WinActive(). So if it'll work there, you can use it!
Yours looks to work really great, so I stop to develop mine.
A few suggestion:
- when I type in a value to search, it should find the label or function even if my entry is in the middle of its name
- - this way you can enter : to get all labels or () all functions as well :!:
- list hotkeys?
- update position of GUI when SciTE is resized/moved
@ fincs: please include in SciTE4AutoHotkey :!:
here is what I have so far using LowLevel:
- Require AutoHotkey 1.47.06 because of LowLevel
- Requre Remote Buffer (included in script)
There is one script that will get the text from Scintilla1 and second that will run trough pipe containing the text from scintilla1 and it will get all functions, labels and hotkeys using lowlevel functions.
First script:
DetectHiddenWindows, On FileRead,script,%A_ScriptDir%\LnF_.ahk IfWinExist, ahk_class SciTEWindow { ControlGet, hwnd,hwnd,,Scintilla1,ahk_class SciTEWindow clipboard:="Goto, #__CREATE_WINDOW`n" . ScintillaGetText(hwnd) . "`n" . script pid := RuntempScript("Goto, #__CREATE_WINDOW`n" . ScintillaGetText(hwnd) . "`n" . script,"Functions and Labels 4 Sci4AutoHotkey") WinWait, ahk_id "pid" hwnd:= WinExist("ahk_pid " . pid) } Else ExitApp WinWaitClose,ahk_id %hwnd% MsgBox Process, Exist,%pid% If !ErrorLevel Process, Close,%pid% ExitApp ScintillaGetLength(hwnd){ static SCI_GETLENGTH:=2006 SendMessage, SCI_GETLENGTH, 0, 0,, ahk_id %hwnd% Return ErrorLevel+1 } ScintillaGetText(hwnd){ static SCI_GETLENGTH:=2006, SCI_GETTEXT:=2182 SendMessage, SCI_GETLENGTH, 0, 0,, ahk_id %hwnd% length := ErrorLevel RemoteBuf_Open(hSBuf, hwnd, length + 2) SendMessage, SCI_GETTEXT, length + 1, RemoteBuf_Get(hSBuf),, ahk_id %hwnd% ;test SendMessage, 2024, 309, RemoteBuf_Get(hSBuf),, ahk_id %hwnd% VarSetCapacity(sText, length + 2) RemoteBuf_Read(hSBuf, sText, length + 2) RemoteBuf_Close(hSBuf) Return sText } ; Title: Remote Buffer ; *Read and write process memory* ; /*------------------------------------------------------------------------------- Function: Open Open remote buffer Parameters: H - Reference to variable to receive remote buffer handle hwnd - HWND of the window that belongs to the process size - Size of the buffer Returns: Error message on failure */ RemoteBuf_Open(ByRef H, hwnd, size) { static MEM_COMMIT=0x1000, PAGE_READWRITE=4 WinGet, pid, PID, ahk_id %hwnd% hProc := DllCall( "OpenProcess", "uint", 0x38, "int", 0, "uint", pid) ;0x38 = PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE IfEqual, hProc,0, return A_ThisFunc "> Unable to open process (" A_LastError ")" bufAdr := DllCall( "VirtualAllocEx", "uint", hProc, "uint", 0, "uint", size, "uint", MEM_COMMIT, "uint", PAGE_READWRITE) IfEqual, bufAdr,0, return A_ThisFunc "> Unable to allocate memory (" A_LastError ")" ; Buffer handle structure: ; @0: hProc ; @4: size ; @8: bufAdr VarSetCapacity(H, 12, 0 ) NumPut( hProc, H, 0) NumPut( size, H, 4) NumPut( bufAdr, H, 8) } /*---------------------------------------------------- Function: Close Close the remote buffer Parameters: H - Remote buffer handle */ RemoteBuf_Close(ByRef H) { static MEM_RELEASE = 0x8000 handle := NumGet(H, 0) IfEqual, handle, 0, return A_ThisFunc "> Invalid remote buffer handle" adr := NumGet(H, 8) r := DllCall( "VirtualFreeEx", "uint", handle, "uint", adr, "uint", 0, "uint", MEM_RELEASE) ifEqual, r, 0, return A_ThisFunc "> Unable to free memory (" A_LastError ")" DllCall( "CloseHandle", "uint", handle ) VarSetCapacity(H, 0 ) } /*---------------------------------------------------- Function: Read Read from the remote buffer into local buffer Parameters: H - Remote buffer handle pLocal - Reference to the local buffer pSize - Size of the local buffer pOffset - Optional reading offset, by default 0 Returns: TRUE on success or FALSE on failure. ErrorMessage on bad remote buffer handle */ RemoteBuf_Read(ByRef H, ByRef pLocal, pSize, pOffset = 0){ handle := NumGet( H, 0), size:= NumGet( H, 4), adr := NumGet( H, 8) IfEqual, handle, 0, return A_ThisFunc "> Invalid remote buffer handle" IfGreaterOrEqual, offset, %size%, return A_ThisFunc "> Offset is bigger then size" VarSetCapacity( pLocal, pSize ) return DllCall( "ReadProcessMemory", "uint", handle, "uint", adr + pOffset, "uint", &pLocal, "uint", size, "uint", 0 ), VarSetCapacity(pLocal, -1) } /*---------------------------------------------------- Function: Write Write local buffer into remote buffer Parameters: H - Remote buffer handle pLocal - Reference to the local buffer pSize - Size of the local buffer pOffset - Optional writting offset, by default 0 Returns: TRUE on success or FALSE on failure. ErrorMessage on bad remote buffer handle */ RemoteBuf_Write(Byref H, byref pLocal, pSize, pOffset=0) { handle:= NumGet( H, 0), size := NumGet( H, 4), adr := NumGet( H, 8) IfEqual, handle, 0, return A_ThisFunc "> Invalid remote buffer handle" IfGreaterOrEqual, offset, %size%, return A_ThisFunc "> Offset is bigger then size" return DllCall( "WriteProcessMemory", "uint", handle,"uint", adr + pOffset,"uint", &pLocal,"uint", pSize, "uint", 0 ) } /*---------------------------------------------------- Function: Get Get address or size of the remote buffer Parameters: H - Remote buffer handle pQ - Query parameter: set to "adr" to get address (default), to "size" to get the size or to "handle" to get Windows API handle of the remote buffer. Returns: Address or size of the remote buffer */ RemoteBuf_Get(ByRef H, pQ="adr") { return pQ = "adr" ? NumGet(H, 8) : pQ = "size" ? NumGet(H, 4) : NumGet(H) } /*--------------------------------------------------------------------------------------- Group: Example (start code) ;get the handle of the Explorer window WinGet, hw, ID, ahk_class ExploreWClass ;open two buffers RemoteBuf_Open( hBuf1, hw, 128 ) RemoteBuf_Open( hBuf2, hw, 16 ) ;write something str := "1234" RemoteBuf_Write( hBuf1, str, strlen(str) ) str := "_5678" RemoteBuf_Write( hBuf1, str, strlen(str), 4) str := "_testing" RemoteBuf_Write( hBuf2, str, strlen(str)) ;read RemoteBuf_Read( hBuf1, str, 10 ) out = %str% RemoteBuf_Read( hBuf2, str, 10 ) out = %out%%str% MsgBox %out% ;close RemoteBuf_Close( hBuf1 ) RemoteBuf_Close( hBuf2 ) (end code) */ /*------------------------------------------------------------------------------------------------------------------- Group: About o Ver 2.0 by majkinetor. See http://www.autohotkey.com/forum/topic12251.html o Code updates by infogulch o Licenced under Creative Commons Attribution-Noncommercial <http://creativecommons.org/licenses/by-nc/3.0/>. */ /* Many thanks to Lexikos@ahk */ RunTempScript(TempScript, name="") { If name = pipe_name := A_TickCount Else pipe_name := name ; Before reading the file, AutoHotkey calls GetFileAttributes(). This causes ; the pipe to close, so we must create a second pipe for the actual file contents. ; Open them both before starting AutoHotkey, or the second attempt to open the ; "file" will be very likely to fail. The first created instance of the pipe ; seems to reliably be "opened" first. Otherwise, WriteFile would fail. pipe_ga := CreateNamedPipe(pipe_name, 2) pipe := CreateNamedPipe(pipe_name, 2) if (pipe=-1 or pipe_ga=-1) { MsgBox, 0,Error,Error, please try again ExitApp } Run, %A_AhkPath% "\\.\pipe\%pipe_name%",,,PID ; Wait for AutoHotkey to connect to pipe_ga via GetFileAttributes(). DllCall("ConnectNamedPipe","uint",pipe_ga,"uint",0) ; This pipe is not needed, so close it now. (The pipe instance will not be fully ; destroyed until AutoHotkey also closes its handle.) DllCall("CloseHandle","uint",pipe_ga) ; Wait for AutoHotkey to connect to open the "file". DllCall("ConnectNamedPipe","uint",pipe,"uint",0) ; AutoHotkey reads the first 3 bytes to check for the UTF-8 BOM "". If it is ; NOT present, AutoHotkey then attempts to "rewind", thus breaking the pipe. Script := chr(239) chr(187) chr(191) TempScript if !DllCall("WriteFile","uint",pipe,"str",Script,"uint",StrLen(Script)+1,"uint*",0,"uint",0) MsgBox, 0,Error,Check Syntax, 4 ;WriteFile failed: %ErrorLevel%/%A_LastError% DllCall("CloseHandle","uint",pipe) Return PID } CreateNamedPipe(Name, OpenMode=3, PipeMode=0, MaxInstances=255) { return DllCall("CreateNamedPipe","str","\\.\pipe\" Name,"uint",OpenMode ,"uint",PipeMode,"uint",MaxInstances,"uint",0,"uint",0,"uint",0,"uint",0) }
Second Script (Execuded trough Pipe):
Suspend, On #SingleInstance, force ;#ErrorStdOut #__CREATE_WINDOW: IfWinExist, Functions and Labels 4 Sci4AutoHotkey ahk_class AutoHotkeyGUI WinClose, Functions and Labels 4 Sci4AutoHotkey ahk_class AutoHotkeyGUI #_#_Get_ALL_#_#(function,label) Gui,98:+ToolWindow +LastFound -Caption +Resize Gui, 98:Margin,0,0 Gui,98:Default mainhwnd:= WinExist(),todo:="Function|Label" sciTEhwnd:=WinExist("ahk_class SciTEWindow") WinGetPos,x,y,w,h,ahk_id %sciTEhwnd% ControlGetPos,sx,sy,sw,sh,Scintilla1,ahk_id %sciTEhwnd% ControlGet,scintilla1hwnd,hwnd,,Scintilla1,ahk_id %sciTEhwnd% Gui, 98:Add,Button,w300 Center g98GuiClose, &Hide fuctions and labels list Gui,98:Add,ListView,% "r" . Round((sh-90)/13) . " w300 xs y+5 g#JumpTo v#Functions",Line#|Count|Type|Name Loop,Parse,todo,| If (todo:=A_LoopField) Loop,Parse,%A_LoopField%,`n If A_LoopField LV_Add("",SubStr(A_LoopField,1,InStr(A_LoopField,".")-1),A_Index,todo,SubStr(A_LoopField,InStr(A_LoopField,".")+1)) LV_ModifyCol(3), LV_ModifyCol(4), LV_ModifyCol(1, "Integer"), LV_ModifyCol(2, "Integer") ;Parent_Handle := DllCall( "FindWindowEx", "uint",0, "uint",0, "str", sciTEhwnd, "uint",0) Gui,98:Show,% "x" . x+sx+sw-322 . " y" . y+sy . " h" . sh ;DllCall( "SetParent", "uint", mainhwnd, "uint", Parent_Handle ) ; success = handle to previous parent, failure =null WinActivate,ahk_id %sciTEhwnd% SetTimer, #WinMove,100 Return ;WinWaitClose, ahk_id `%mainhwnd`% 98GuiClose: ExitApp Return #WinMove: IfWinNotExist,ahk_id %sciTEhwnd% ExitApp IfWinActive,ahk_id %sciTEhwnd% WinSet,AlwaysOnTop, On,ahk_id %mainhwnd% IfWinNotActive,ahk_id %sciTEhwnd% IfWinNotActive,ahk_id %mainhwnd% WinSet,AlwaysOnTop, Off,ahk_id %mainhwnd% WinGetPos,x,y,w,h,ahk_id %sciTEhwnd% ;MsgBox % x "." y ControlGetPos,sx,sy,sw,sh,Scintilla1,ahk_id %sciTEhwnd% ;MsgBox % sx "." sy "." sw "`n" "x" . x+sx+sw-323 WinMove,ahk_id %mainhwnd%,,x+sx+sw-323,y+sy,sw<400 ? sw/2,sh SendMessage, 0x214,1,0,,ahk_id %mainhwnd% Return 98GuiSize: #__Anchor_("button1","w") #__Anchor_("SysListView321","wh") Return #JumpTo: If !(A_GuiEvent="DoubleClick") Return LV_GetText(pos,A_EventInfo) SendMessage,2024,pos,0,,ahk_id %scintilla1hwnd% WinActivate,ahk_id %sciTEhwnd% Return /* Function: Anchor Defines how controls should be automatically positioned relative to the new dimensions of a GUI when resized. Parameters: cl - a control HWND, associated variable name or ClassNN to operate on a - (optional) one or more of the anchors: 'x', 'y', 'w' (width) and 'h' (height), optionally followed by a relative factor, e.g. "x h0.5" r - (optional) true to redraw controls, recommended for GroupBox and Button types Examples: > "xy" ; bounds a control to the bottom-left edge of the window > "w0.5" ; any change in the width of the window will resize the width of the control on a 2:1 ratio > "h" ; similar to above but directrly proportional to height Remarks: Anchor must always be called within a GuiSize label where AutoHotkey assigns a real value to A_Gui. The only exception is when the second and third parameters are omitted to reset the stored positions for a control. For a complete example see anchor-example.ahk. License: - Version 4.56 by Titan <https://ahknet.autohotkey.com/~Titan/#anchor> - GNU General Public License 3.0 or higher <http://www.gnu.org/licenses/gpl-3.0.txt> */ /* Function: Anchor Defines how controls should be automatically positioned relative to the new dimensions of a GUI when resized. Parameters: cl - a control HWND, associated variable name or ClassNN to operate on a - (optional) one or more of the anchors: 'x', 'y', 'w' (width) and 'h' (height), optionally followed by a relative factor, e.g. "x h0.5" r - (optional) true to redraw controls, recommended for GroupBox and Button types Examples: > "xy" ; bounds a control to the bottom-left edge of the window > "w0.5" ; any change in the width of the window will resize the width of the control on a 2:1 ratio > "h" ; similar to above but directrly proportional to height Remarks: Anchor must always be called within a GuiSize label where AutoHotkey assigns a real value to A_Gui. The only exception is when the second and third parameters are omitted to reset the stored positions for a control. For a complete example see anchor-example.ahk. License: - Version 4.56 by Titan <https://ahknet.autohotkey.com/~Titan/#anchor> - GNU General Public License 3.0 or higher <http://www.gnu.org/licenses/gpl-3.0.txt> */ #__Anchor_(i, a = "", r = false) { static c, cs = 12, cx = 255, cl = 0, g, gs = 8, z = 0, k = 0xffff, gx = 1 If z = 0 VarSetCapacity(g, gs * 99, 0), VarSetCapacity(c, cs * cx, 0), z := true If a = { StringLeft, gn, i, 2 If gn contains : { StringTrimRight, gn, gn, 1 t = 2 } StringTrimLeft, i, i, t ? t : 3 If gn is not digit gn := gx } Else gn := A_Gui If i is not xdigit { GuiControlGet, t, Hwnd, %i% If ErrorLevel = 0 i := t Else ControlGet, i, Hwnd, , %i% } gb := (gn - 1) * gs Loop, %cx% If (NumGet(c, cb := cs * (A_Index - 1)) == i) { If a = { cf = 1 Break } Else gx := A_Gui d := NumGet(g, gb), gw := A_GuiWidth - (d >> 16 & k), gh := A_GuiHeight - (d & k), as := 1 , dx := NumGet(c, cb + 4, "Short"), dy := NumGet(c, cb + 6, "Short") , dw := NumGet(c, cb + 8, "Short"), dh := NumGet(c, cb + 10, "Short") Loop, Parse, a, xywh If A_Index > 1 av := SubStr(a, as, 1), as += 1 + StrLen(A_LoopField) , d%av% += (InStr("yh", av) ? gh : gw) * (A_LoopField + 0 ? A_LoopField : 1) DllCall("SetWindowPos", "UInt", i, "Int", 0, "Int", dx, "Int", dy, "Int", dw, "Int", dh, "Int", 4) If r != 0 DllCall("RedrawWindow", "UInt", i, "UInt", 0, "UInt", 0, "UInt", 0x0101) ; RDW_UPDATENOW | RDW_INVALIDATE Return } If cf != 1 cb := cl, cl += cs If (!NumGet(g, gb)) { Gui, %gn%:+LastFound WinGetPos, , , , gh VarSetCapacity(pwi, 68, 0), DllCall("GetWindowInfo", "UInt", WinExist(), "UInt", &pwi) , NumPut(((bx := NumGet(pwi, 48)) << 16 | by := gh - A_GuiHeight - NumGet(pwi, 52)), g, gb + 4) , NumPut(A_GuiWidth << 16 | A_GuiHeight, g, gb) } Else d := NumGet(g, gb + 4), bx := d >> 16, by := d & k ControlGetPos, dx, dy, dw, dh, , ahk_id %i% If cf = 1 { Gui, %gn%:+LastFound WinGetPos, , , gw, gh d := NumGet(g, gb), dw -= gw - bx * 2 - (d >> 16), dh -= gh - by - bx - (d & k) } NumPut(i, c, cb), NumPut(dx - bx, c, cb + 4, "Short"), NumPut(dy - by, c, cb + 6, "Short") , NumPut(dw, c, cb + 8, "Short"), NumPut(dh, c, cb + 10, "Short") Return, true } #_#_Get_ALL_#_#(ByRef functions,ByRef labels){ __init() pfunc := __getFirstFunc() Loop { If (NumGet(pfunc+49,0,"CHR")="1") { pfunc := NumGet(pfunc+44, 0, "UInt") Continue } pName := NumGet(pfunc+0, 0, "UInt") pLine := NumGet(pfunc+4, 0, "UInt") If !pFunc break pfunc := NumGet(pfunc+44, 0, "UInt") var .= NumGet(pLine+8,0,"UInt")-2 Loop { if !NumGet(pName+A_Index-1,0,"Uchr") break varlabel .=Chr(NumGet(pName+A_Index-1,0,"CHR")) } If varlabel not in __init,__getVar,__findFunc,__getFirstFunc,__GetFirstLabel,#__Anchor_,#_#_Get_ALL_#_#,__str ,__getFuncUDF,__getFirstLine,__findLabel,__mcode,__mcode__getVar,__getFirstLabelDllCall,__getFirstLabelDllCallchr ,__getFirstLabelDllCallchrStrLen,__getFirstLabelDllCallchrStrLenWinExist,__getFirstLabelDllCallchrStrLenWinExistLV_Add ,__getFirstLabelDllCallchrStrLenWinExistLV_AddSubStr,__getFirstLabelDllCallchrStrLenWinExistLV_AddSubStrInStr ,__getFirstLabelDllCallchrStrLenWinExistLV_AddSubStrInStrLV_ModifyCol ,__getFirstLabelDllCallchrStrLenWinExistLV_AddSubStrInStrLV_ModifyColLV_GetText ,__getFirstLabelDllCallchrStrLenWinExistLV_AddSubStrInStrLV_ModifyColLV_GetTextNumGet ,__getFirstLabelDllCallchrStrLenWinExistLV_AddSubStrInStrLV_ModifyColLV_GetTextNumGetNumPut ,__getFirstLabelDllCallchrStrLenWinExistLV_AddSubStrInStrLV_ModifyColLV_GetTextNumGetNumPutRegisterCallback ,__getFirstLabelDllCallchrStrLenWinExistLV_AddSubStrInStrLV_ModifyColLV_GetTextNumGetNumPutRegisterCallbackVarsetCapacity functions .= var . "." . varlabel . (!NumGet(pfunc+44, 0, "UInt") ? "" : "`n") var= varlabel= } pFunc := __getFirstLabel() Loop { pName := NumGet(pfunc+0, 0, "UInt") pLine := NumGet(pfunc+4, 0, "UInt") var .= NumGet(pLine+8,0,"UInt")-2 Loop { if !NumGet(pName+A_Index-1,0,"Uchr") break varlabel .=Chr(NumGet(pName+A_Index-1,0,"CHR")) } If varlabel not in #JumpTo,#__CREATE_WINDOW,__getFirstLabel_label,98GuiSize,98GuiClose,#WinMove labels .= var . "." . varlabel . "`n" var= varlabel= pfunc := NumGet(pfunc+12, 0, "UInt") If !pFunc break } } __init() { ; __getFirstFunc must be called at least once before (or by) __mcode, or it won't work properly later on. ;__getFirstFunc() __mcode("__getVar","8B4C24088B0933C08379080375028B018B4C2404998901895104C3") ;__mcode("__init","C3") } __mcode(FuncName, Hex) { if !(pFunc := __findFunc(FuncName)) or !(pbin := DllCall("GlobalAlloc","uint",0,"uint",StrLen(Hex)//2)) return 0 Loop % StrLen(Hex)//2 NumPut("0x" . SubStr(Hex,2*A_Index-1,2), pbin-1, A_Index, "char") NumPut(pbin,pFunc+4), NumPut(1,pFunc+49,0,"char") return pbin } __getVar(var) { ; This function is implemented in machine code. See __init(). } __findFunc(FuncName, FirstFunc=0) { if !FirstFunc { ; __getFuncUDF, in-line: if pCb := RegisterCallback(FuncName) { pFunc := NumGet(pCb+28) DllCall("GlobalFree","uint",pCb) if pFunc return pFunc } ; end __getFuncUDF. if !(FirstFunc := __getFirstFunc()) return 0 } pFunc := FirstFunc Loop { if __str(NumGet(pFunc+0)) = FuncName ; pFunc->mName return pFunc if ! pFunc := NumGet(pFunc+44) ; pFunc->mNextFunc return 0 } } ; How __findFunc works: ; - If we weren't given a list to search (via FirstFunc), try RegisterCallback. ; RegisterCallback fails if the function is built-in or has ByRef parameters. ; - Built-in functions do not exist in the linked list until they are either ; referenced in script or "searched for" by calling RegisterCallback. ; - To access the linked list of functions, __getFirstFunc searches through all ; function derefs in the script. We then search the linked list. __getFirstFunc() { static pFirstFunc if !pFirstFunc { if !(pLine := __getFirstLine()) return 0 Loop { Loop % NumGet(pLine+1,0,"uchar") { ; pLine->mArgc pArg := NumGet(pLine+4) + (A_Index-1)*12 ; pLine->mArg[A_Index-1] if (NumGet(pArg+0,0,"uchar") != 0) ; pArg->type != ARG_TYPE_NORMAL continue ; arg has no derefs (only a Var*) Loop { pDeref := NumGet(pArg+8) + (A_Index-1)*12 ; pArg->deref[A_Index-1] if (!NumGet(pDeref+0)) ; pDeref->marker (NULL terminates list) break if (NumGet(pDeref+8,0,"uchar")) ; pDeref->is_function { ; The first function is either the first defined function, ; or if no explicitly #included UDFs exist, the first ; built-in function referenced in code. pFunc := NumGet(pDeref+4) if (NumGet(pFunc+49,0,"uchar")) { ; pFunc->mIsBuiltIn if !pFirstBIF pFirstBIF := pFunc } else { ; UDF pFuncLine := NumGet(pFunc+4) FuncLine := NumGet(pFuncLine+8) FuncFile := NumGet(pFuncLine+2,0,"ushort") if !pFirstFunc or (FuncFile < FirstFuncFile || (FuncFile = FirstFuncFile && FuncLine < FirstFuncLine)) pFirstFunc:=pFunc, FirstFuncLine:=FuncLine, FirstFuncFile:=FuncFile } } } } if !(pLine:=NumGet(pLine+20)) ; pLine->mNextLine break } if pFirstBIF { ; Usually the first UDF will be before the first BIF, but not if ; only auto-included/stdlib UDFs exist *AND* the first BIF is ; referenced in code before the first UDF. if pFirstFunc { ; Look for the BIF using the UDF as a starting point. pFunc := pFirstFunc Loop { if !(pFunc := NumGet(pFunc+44)) ; pFunc->mNextFunc break ; If the BIF is found, the UDF must precede it in the list. if (pFunc = pFirstBIF) return pFirstFunc } ; If we got here, the BIF was not found, so is probably the first Func. } pFirstFunc := pFirstBIF return pFirstFunc } } return pFirstFunc } __str(addr,len=-1) { if len<0 return DllCall("MulDiv","uint",addr,"int",1,"int",1,"str") VarSetCapacity(str,len), DllCall("lstrcpyn","str",str,"uint",addr,"int",len+1) return str } __getFuncUDF(FuncName) { if pCb := RegisterCallback(FuncName) { func := NumGet(pCb+28) DllCall("GlobalFree","uint",pCb) } return func } __getFirstLine() { static pFirstLine if (pFirstLine = "") { if pThisFunc := __getFuncUDF(A_ThisFunc) { if pFirstLine := NumGet(pThisFunc+4) ; mJumpToLine Loop { if !(pLine:=NumGet(pFirstLine+16)) ; mPrevLine break pFirstLine := pLine } } } return pFirstLine } __findLabel(LabelName, FirstLabel=0) { if !FirstLabel && !(FirstLabel := __getFirstLabel()) return 0 pLabel := FirstLabel Loop { if __str(NumGet(pLabel+0)) = LabelName return pLabel if ! pLabel := NumGet(pLabel+12) return 0 } } __getFirstLabel() { static pFirstLabel if !pFirstLabel { if !(pLine := NumGet(__getFuncUDF(A_ThisFunc)+4)) return 0 Loop { act := NumGet(pLine+0,0,"char") if (act = 96 || act = 95) ; ACT_GOSUB || ACT_GOTO break if !(pLine:=NumGet(pLine+20)) ; pLine->mNextLine return 0 } pFirstLabel := NumGet(pLine+24) Loop { if ! pPrevLabel:=NumGet(pFirstLabel+8) break pFirstLabel := pPrevLabel } } return pFirstLabel ; Since Labels are in a doubly-linked list, we can find the first label ; by getting the Label associated with the goto line below. __getFirstLabel_label: goto __getFirstLabel_label }
Thanks!TheGood, you've got my vote
Using low level functions would have probably resulted in faster script scanning, but much to my surprise, using RegEx isn't that slow, ie. you don't really notice it (I tried with my biggest script and had no lag).
Great idea! I'll implement it tonight (it's a one-liner). Or maybe I'll make it a configurable thing, so that you can toggle between the two styles.A few suggestion:
- when I type in a value to search, it should find the label or function even if my entry is in the middle of its name
- - this way you can enter : to get all labels or () all functions as well :!:
Good idea also. That one will be a tad harder, but shouldn't take long.- list hotkeys?
The GUI should position itself correctly when you call it. Therefore if you move the window while the GUI is open, you can just close the GUI and reopen it.- update position of GUI when SciTE is resized/moved
Do you mean actively moving the GUI while it's open in case the editor moves around? I don't know if I like that because it would add some overhead processing for something that probably won't happen often (ie. why would you want to move your editor if you're looking for a function/label?). And it can easily be circumvented by pressing the hotkey to hide it again before the user starts moving the editor.
If(blah){ is treated as a function.
The function names "If" and "While" should be excluded from the list
Here is a fix for the function:
;This sub analyses the script and add the functions in it to the array GetScriptFunctions(ByRef s) { Local i, t ;Loop through the functions sFuncs0 := 0 ;Prep counter i := 1 Loop { ;Get the next function i := RegExMatch(s, "m)^[[:blank:]]*\K[a-zA-Z0-9#_@\$\?\[\]]+(?=\(.*?\)\s*?\{)", t, i) ;Check if we found something If (i = 0) Break ; fincs-change: Don't allow If or While as function. If(!((t = "If") || (t = "While"))){ ;Increment counter sFuncs0 += 1 sFuncs%sFuncs0% := t "()" ;Add the () sFuncs%sFuncs0%_Line := LineFromPos(i) } ;Get the next function i := InStr(s, "`n", False, i) + 1 If (i = 1) Break } }
Yours seems to do it very quickly, I'm not sure if lowlevel could be faster as you need to restart the script on changes and this takes a little.Thanks!TheGood, you've got my vote
Using low level functions would have probably resulted in faster script scanning, but much to my surprise, using RegEx isn't that slow, ie. you don't really notice it (I tried with my biggest script and had no lag).
The GUI should position itself correctly when you call it. Therefore if you move the window while the GUI is open, you can just close the GUI and reopen it.- update position of GUI when SciTE is resized/moved
Do you mean actively moving the GUI while it's open in case the editor moves around? I don't know if I like that because it would add some overhead processing for something that probably won't happen often (ie. why would you want to move your editor if you're looking for a function/label?). And it can easily be circumvented by pressing the hotkey to hide it again before the user starts moving the editor.
You are probably right, It is far not that important. It just does not look professional when it moves aside SciTE.
Possibly it would be enough if it gets hidden?!
- Position the cursor in the list so you can jump to the function/label (by typing a letter) and move up and down straight away.
- - Mostly you do not have that many functions/labels so this would be more usable I think.
- Hide/Set AlwaysOnTop Off when another window gets on top of SciTE!
- Gui,Margin,0,0 (Looks nicer I think)
- For SciTE, Instead of hotkey, it should start up directly showing list and exits (exitapp) on hide/close
- Leave column header for sorting purpose and add second column displaying line number
- Display parameters in functions. what do you think?
- Dock to top right corner?
- Optional transparency for GUI?
Also, the script now has less margin. It looks better than before. Thanks HotkeyIt for the :idea:.
The match can now be anywhere (thanks HotkeyIt for the :idea: again). The feature is turned on by default, but you can turn it off to go back to the old school behaviour. From the script header:
Also, HotkeyIt, I'm doing this at work right now but I'll reply to the other features you suggested tonight.bMatchEverywhere := True
;Set to False for matching to occur only at the beginning of the label/function
;name. Set to True for matching to occur anywhere in the label/function name. As
;well, multiple words can be specified. For example, typing "Dog Cat" will match
;any label/function containing those words anywhere in their name. This is useful
;to search for functions (or labels) only by typing "() FunctionName".
Do you mean focus when you say cursor? If so, the textbox already catches up and down so as to send it to the listbox. Therefore, you wouldn't need to focus it first anyways. Or did you mean the actual mouse cursor? I don't know if that's a good idea because everything can be done through the keyboard so much faster.Position the cursor in the list so you can jump to the function/label (by typing a letter) and move up and down straight away.
- - Mostly you do not have that many functions/labels so this would be more usable I think.
The GUI now simply hides when selecting a window other than an editor- Hide/Set AlwaysOnTop Off when another window gets on top of SciTE!
Good idea! It looks much nicer now. Although I used Gui, Margin, 2, 2. Using 0 looked a little too skinny, and the borders weren't pronounced enough.- Gui,Margin,0,0 (Looks nicer I think)
I'll make some experiments tonight, but I don't think that's a good idea. Don't forget that AutoHotkey has to do some pre-compiling before executing. As the script gets bigger, it might become a noticeable lag upon execution. Again, I'll try it and see how it feels.- For SciTE, Instead of hotkey, it should start up directly showing list and exits (exitapp) on hide/close
Maybe. You need to convince me lol. Why would you need the line number also shown? It seems like distracting information to me.- Leave column header for sorting purpose and add second column displaying line number
Possibly. You need to convince me on that one too lol. Again, it might seem like distracting info. One thing I could do is add a label under the listbox which shows the parameters of the selected function (although the text would probably run off for some of them). But again, why do you need that info?- Display parameters in functions. what do you think?
Yeah, I'm thinking moving it too actually. try this for lines 497 and tell me what you think:- Dock to top right corner?
;Calculate location and show it iX += iW - iGUIWidth - 30 ; - 50 iY += 30 ;110
Yes, I definitely agree! I'll add it in the next release.- Optional transparency for GUI?
- Added the ability to select transparency (see iTransparency in script)
- GUI now shows up in the corner of the window. Let me know what you think.
- Made the script 1024x768 friendly (ie. cut long lines in shorter ones)
Edit (instead of another post):
- Added support for hotkey scanning as well!
Edit 2:
- Restructured script to be more organized
- Added support for clone mode in Notepad++
- Made minor optimizations
Do you mean focus when you say cursor? If so, the textbox already catches up and down so as to send it to the listbox. Therefore, you wouldn't need to focus it first anyways. Or did you mean the actual mouse cursor? I don't know if that's a good idea because everything can be done through the keyboard so much faster.
I didn't notice that, this is great, forget about mine
I'll make some experiments tonight, but I don't think that's a good idea. Don't forget that AutoHotkey has to do some pre-compiling before executing. As the script gets bigger, it might become a noticeable lag upon execution. Again, I'll try it and see how it feels.
From my experiments I can say it is acceptable. Use this optionally by using a parameter from SciTE?
Maybe. You need to convince me lol. Why would you need the line number also shown? It seems like distracting information to me.
This keeps the order of the functions and labels as they appear in the script, this is often more usable than sort by name, I think.
About parameters I am not sure as well if that is necessary.
Try this regarding position, looks much better I think.
WinGetPos, iX, iY, iW, iH, ahk_id %hNPP% [color=Red]ControlGetPos,sx,sy,sw,sh,%cSci%,ahk_id %hNPP%[/color] Gui, Show, w0 h0 WinSet, Transparent, 0, ahk_id %hGui% [color=Red]Gui, Show,% "AutoSize x" . iX+sx+sw-(iGUIWidth+4)-15 . " y" . iY+sy[/color]
Try this regarding position, looks much better I think.
WinGetPos, iX, iY, iW, iH, ahk_id %hNPP% [color=Red]ControlGetPos,sx,sy,sw,sh,%cSci%,ahk_id %hNPP%[/color] Gui, Show, w0 h0 WinSet, Transparent, 0, ahk_id %hGui% [color=Red]Gui, Show,% "AutoSize x" . iX+sx+sw-(iGUIWidth+4)-15 . " y" . iY+sy[/color]
I like the idea of using the scintilla coords! How about this (it makes the GUI line up with the scintilla border and the scrollbar):
;Get window info WinGetPos, iX, iY,,, ahk_id %hNPP% ControlGetPos, sX, sY, sW, sH, %cSci%, ahk_id %hNPP% iX += sX + sW - (iGUIWidth + 2) iY += sY Gui, Show, w0 h0 WinSet, Transparent, 0, ahk_id %hGui% Gui, Show, AutoSize x%iX% y%iY%
I'll work on the line numbers tonight. The only thing is that I would have to use a listview instead which I don't like too much. I'll see if I can keep the listbox and add the line numbers somehow.
bQuickMode := False ;Set to True to make TillaGoto go straight to showing the GUI and exit on close.
The position is exactly what I wanted but it should not hover the scrollbar.
Here is my version (includes latest changes).
It is attached a little better I think and the rolldown and rollup effect looks cool 8) What do you think :?:
/* TheGood TillaGoto - Go to functions and labels in your script */ ;_________________________________ ;CONFIGURATION uHotkey := "F1" ;Specify the hotkey you want to use to call up the GUI iGUIWidth := 200 ;Specify the width of the GUI iGUIHeight := 10 ;Specify, in number of rows, the height of the listbox iTransparency := 255 ;Specify the transparency of the GUI. Preferably divisible by 15. Put 255 for no ;transparency at all (consumes less resources). Put 0 to disable fade-in effect. bQuickMode := False ;Set to True to make TillaGoto go straight to showing the GUI and exit on close. bMatchEverywhere := True ;Set to False for matching to occur only at the beginning of the label/function ;name. Set to True for matching to occur anywhere in the label/function name. As ;well, multiple words can be specified. For example, typing "Dog Cat" will match ;any label/function containing those words anywhere in their name. This is useful ;to search for functions (or labels) only by typing "() FunctionName". sActiveWindow := "\.ahk - Notepad\+\+$" ;Regular expression which should match the window of the editor ;containing the Scintilla control. Use "ahk - SciTE4AutoHotkey" ;for SciTE4AutoHotkey sScintillaClass := "Scintilla" ;Class name of the Scintilla control. Exclude instance number. ;______________________________________ ;DO NOT CHANGE ANYTHING BELOW THIS LINE #Include %A_ScriptDir%\RemoteBuf.ahk #EscapeChar @ #SingleInstance Force SetTitleMatchMode, RegEx ;Create GUI Gui, +AlwaysOnTop +Border +ToolWindow +LastFound -Caption Gui, Font, s8, Courier New Gui, Margin, 2, 2 Gui, Add, Edit, w%iGUIWidth% h20 vtxtSearch gtxtSearch_Event hwndhtxtsearch, Gui, Add, ListBox, Sort wp r%iGUIHeight% vlblList glblList_Event hwndhlblList +HScroll, hGui := WinExist() ;Catch WM_KEYDOWN OnMessage(256, "GUIKeyDown") ;Check if we're in quick mode If bQuickMode { ;Check if Notepad++ is active hNPP := WinActive(sActiveWindow) If Not hNPP ExitApp bExitOnClose := True Gosub SummonGUI } Else bExitOnClose := False ;Main monitoring loop Loop { Sleep, 200 ;Check if Notepad++ is active h := WinActive(sActiveWindow) ;Skip the loop if Notepad++ is not active If Not h And Not WinActive("ahk_id " hGui) { ;Turn off hotkeys Hotkey, %uHotkey%, SummonGUI, Off ;Hide GUI if showing If bShowing Gosub, GuiEscape hNPP := 0 ;Reset value Continue ;Check if we just found a new window } Else If (h <> hNPP) And Not WinActive("ahk_id " hGui) { ;Remember the newfound Notepad++ window and script hNPP := h ;Turn on hotkeys Hotkey, %uHotkey%, SummonGUI, On } } ;------------\ ;GUI related | ;------------/ ;User summoned the GUI SummonGUI: ;Check if we're already showing If bShowing Goto GuiEscape ;Get handle to focused control ControlGetFocus, cSci, ahk_id %hNPP% ;Check if it fits the class name If InStr(cSci, sScintillaClass) ControlGet, hSci, Hwnd,, %cSci%, ahk_id %hNPP% Gosub, AnalyseScript ;Check if text is selected s := Sci_GetSelText(hSci) If (s <> "") And Not InStr(s, "@n") { ;Copy the selected text in the textbox GuiControl,, txtSearch, %s% ;Create a list based on sel CreateList(s) ;Check if it's just one match. If so, go to it. LB_GETCOUNT. SendMessage, 395, 0, 0,, ahk_id %hlblList% If (ErrorLevel = 1) Goto SelectItem ;Select all. EM_SETSEL SendMessage, 177, 0, -1,, ahk_id %htxtSearch% } Else { ;Otherwise, empty the textbox and show the whole list GuiControl,, txtSearch, CreateList() } ;Get window info WinGetPos, iX, iY,,, ahk_id %hNPP% ControlGetPos, sX, sY, sW, sH, %cSci%, ahk_id %hNPP% iX += sX + sW - (iGUIWidth + 2)-18 iY += sY - 0 Gui, Show, w0 h0 WinSet, Transparent, 0, ahk_id %hGui% Gui, Show, AutoSize x%iX% y%iY% bShowing := True GuiShow() ControlFocus,, ahk_id %htxtSearch% Return GuiShow(){ global hGui, tillaH, iTransparency If !tillaH WinGetPos,,,,tillaH,ahk_id %hGui% Gui, Show, h1 NA If Not iTransparency Or (iTransparency = 255) ;Turn off if opaque WinSet, Transparent, OFF, ahk_id %hGui% else WinSet, Transparent, %iTransparency%, ahk_id %hGui% Loop % tillaH/10 { Gui, Show,% "NA h" A_Index*10 Sleep, 10 } Gui, Show,% "NA h" tillaH } GuiHide(){ global hGui,tillaH, iTransparency Loop % tillaH/10 { Gui, Show,% "NA h" tillaH-A_Index*10 Sleep, 10 } Gui, Hide } GuiEscape: bShowing := False Gui, Cancel GuiHide() If bExitOnClose ExitApp Return ;Incremental searching txtSearch_Event: If bShowing { GuiControlGet, s,, txtSearch CreateList(s) } Return lblList_Event: If (A_GuiEvent <> "DoubleClick") Return SelectItem: ;Get selected item index. LB_GETCURSEL SendMessage, 0x188, 0, 0,, ahk_id %hlblList% s := GetListBoxItem(hlblList, ErrorLevel) ;Check if it's a label or a function StringRight, t, s, 1 If (t = ":") { Loop %sLabels0% { If (sLabels%A_Index% = s) { l := sLabels%A_Index%_Line Break } } } Else { Loop %sFuncs0% { If (sFuncs%A_Index% = s) { l := sFuncs%A_Index%_Line Break } } } ;Call up the line in the Scintilla control ShowLine(l) Goto GuiEscape ;Done Return GUIKeyDown(wParam, lParam, msg, hwnd) { Local iCount IfEqual wParam, 13, Gosub SelectItem ;Enter ;Check if it's the textbox If (hwnd = htxtSearch) { If (wParam = 38) { ControlFocus,, ahk_id %hlblList% If Not WrapSel(True) SendInput {Up} } Else If (wParam = 40) { ControlFocus,, ahk_id %hlblList% If Not WrapSel(False) SendInput {Down} } } Else If (hwnd = hlblList) { ;Make up/down wrap around If (wParam = 38) Or (wParam = 40) Return WrapSel(wParam = 38) ? True : "" } } WrapSel(bUp) { Local iCount, iSel ;Get selected item index and count. LB_GETCOUNT. LB_GETCURSEL. SendMessage, 395, 0, 0,, ahk_id %hlblList% iCount := ErrorLevel SendMessage, 392, 0, 0,, ahk_id %hlblList% iSel := ErrorLevel ;Select the first/last item. LB_SETCURSEL If bUp And (iSel = 0) { SendMessage 390, iCount - 1, 0,, ahk_id %hlblList% Return 1 } Else If Not bUp And (iSel = iCount - 1) { SendMessage 390, 0, 0,, ahk_id %hlblList% Return 1 } Return 0 } ;-------------------\ ;Scanning Functions | ;-------------------/ ;Retrieves labels and functions of the script AnalyseScript: ;Get full text sScript := Sci_GetText(hSci) ;Prep it InvalideBlockComments(sScript) ;Get labels and functions GetScriptLabels(sScript) GetScriptHotkeys(sScript) GetScriptFunctions(sScript) Return ;This sub analyses the script and add the labels in it to the array GetScriptLabels(ByRef s) { Local i, t ;Reset counter sLabels0 := 0 i := 1 Loop { ;Get next label s:=RegExReplace(s,"\s;.*") i := RegExMatch(s, "m)^[[:blank:]]*\K[a-zA-Z0-9#_@\$\?\[\]]+:[[:blank:]]*$", t, i) ;Make sure we found something If Not i Break ;We found a label. Trim everything after the last colon StringLeft t, t, InStr(t, ":", False, 0) sLabels0 += 1 ;Increase counter sLabels%sLabels0% := t ;Add to array ;Get line from pos sLabels%sLabels0%_Line := LineFromPos(i) ;Set i to the beginning of the next line i := InStr(s, "@n", False, i) + 1 } } ;This sub analyses the script and add the hotkeys in it to the array (uses the same array as labels) GetScriptHotkeys(ByRef s) { Local i, t i := 1 Loop { ;Get next hotkey looking thing s:=RegExReplace(s,"\s;.*") i := RegExMatch(s, "m)^[[:blank:]]*\K[^;][[:blank:]a-zA-Z0-9\Q#!^+&<>*~$`-=\[]';/\.,\E]+::", t, i) ;Make sure we found something If Not i Break ;We found a hotkey. sLabels0 += 1 ;Increase counter sLabels%sLabels0% := t ;Add to array ;Get line from pos sLabels%sLabels0%_Line := LineFromPos(i) ;Set i to the beginning of the next line i := InStr(s, "@n", False, i) + 1 } } ;This sub analyses the script and add the functions in it to the array GetScriptFunctions(ByRef s) { Local i, t ;Loop through the functions sFuncs0 := 0 ;Prep counter i := 1 Loop { ;Get the next function s:=RegExReplace(s,"\s;.*") i := RegExMatch(s, "m)^[[:blank:]]*\K[a-zA-Z0-9#_@\$\?\[\]]+(?=\(.*?\)\s*?\{)", t, i) ;Check if we found something If (i = 0) Break ;Make sure it's a valid function If t Not In If,While { ;Increment counter sFuncs0 += 1 sFuncs%sFuncs0% := t "()" ;Add the () sFuncs%sFuncs0%_Line := LineFromPos(i) } ;Get the next function i := InStr(s, "@n", False, i) + 1 If (i = 1) Break } } ;This function adds a "!" in front of lines inside comment blocks so that they dont interfere InvalideBlockComments(ByRef s) { i := 0, len := StrLen(s) ;Loop through each block Loop { ;Get next block start i := RegExMatch(s, "m)^[[:blank:]]*/\*", "", i + 1) ;Make sure we got something If i { ;Search for matching */ j := RegExMatch(s, "m)^[[:blank:]]*\*/", "", i + 1) ;Check if we got something If j { ;Every char after a LF must be ! n := i Loop { n := InStr(s, "@n", False, n+1) If (n > j) Or (Not n) Break NumPut(33, s, n+1, "UChar") } ;Do it til the end } Else { ;Every char after a LF must be ! n := i Loop { n := InStr(s, "@n", False, n+1) If (n = len - 1) Or (Not n) Break NumPut(33, s, n+1, "UChar") } } ;We went through all the blocks already } Else Break } } ;------------------\ ;ListBox Functions | ;------------------/ CreateList(filter = "") { Global sLabels0, sFuncs0, bMatchEverywhere, hlblList ;Clear GuiControl,, lblList,| ;Autotrim filter = %filter% If (filter = "") { ;Split cases for speed Loop %sLabels0% GuiControl,, lblList, % sLabels%A_Index% Loop %sFuncs0% GuiControl,, lblList, % sFuncs%A_Index% } Else { ;Split cases for speed If bMatchEverywhere { ;Parse words StringSplit, words, filter, %A_Space% ;Split cases for speed If (words0 > 1) { Loop %sLabels0% { bMatch := True i := A_Index Loop %words0% { bMatch := bMatch And InStr(sLabels%i%, words%A_Index%) If Not bMatch Break } If bMatch GuiControl,, lblList, % sLabels%A_Index% } Loop %sFuncs0% { bMatch := True i := A_Index Loop %words0% { bMatch := bMatch And InStr(sFuncs%i%, words%A_Index%) If Not bMatch Break } If bMatch GuiControl,, lblList, % sFuncs%A_Index% } ;It's one word } Else { Loop %sLabels0% If InStr(sLabels%A_Index%, filter) GuiControl,, lblList, % sLabels%A_Index% Loop %sFuncs0% If InStr(sFuncs%A_Index%, filter) GuiControl,, lblList, % sFuncs%A_Index% } } Else { Loop %sLabels0% If (InStr(sLabels%A_Index%, filter) = 1) GuiControl,, lblList, % sLabels%A_Index% Loop %sFuncs0% If (InStr(sFuncs%A_Index%, filter) = 1) GuiControl,, lblList, % sFuncs%A_Index% } } ;Add hscrollbar if necessary ListBoxAdjustHSB(hlblList) ;Select the first item. LB_SETCURSEL SendMessage 390, 0, 0,, ahk_id %hlblList% } ListBoxAdjustHSB(hLB) { ;Declare variables (for clarity's sake) dwExtent := 0 dwMaxExtent := 0 hDCListBox := 0 hFontOld := 0 hFontNew := 0 VarSetCapacity(lptm, 53) ;Use GetDC to retrieve handle to the display context for the list box and store it in hDCListBox hDCListBox := DllCall("GetDC", "Uint", hLB) ;Send the list box a WM_GETFONT message to retrieve the handle to the ;font that the list box is using, and store this handle in hFontNew SendMessage 49, 0, 0,, ahk_id %hLB% hFontNew := ErrorLevel ;Use SelectObject to select the font into the display context. ;Retain the return value from the SelectObject call in hFontOld hFontOld := DllCall("SelectObject", "Uint", hDCListBox, "Uint", hFontNew) ;Call GetTextMetrics to get additional information about the font being used ;(eg. to get tmAveCharWidth's value) DllCall("GetTextMetrics", "Uint", hDCListBox, "Uint", &lptm) tmAveCharWidth := NumGet(lptm, 20) ;Get item count using LB_GETCOUNT SendMessage 395, 0, 0,, ahk_id %hLB% ;Loop through the items Loop %ErrorLevel% { ;Get list box item text s := GetListBoxItem(hLB, A_Index - 1) ;For each string, the value of the extent to be used is calculated as follows: DllCall("GetTextExtentPoint32", "Uint", hDCListBox, "str", s, "int", StrLen(s), "int64P", nSize) dwExtent := (nSize & 0xFFFFFFFF) + tmAveCharWidth ;Keep if it's the highest to date If (dwExtent > dwMaxExtent) dwMaxExtent := dwExtent } ;After all the extents have been calculated, select the old font back into hDCListBox and then release it: DllCall("SelectObject", "Uint", hDCListBox, "Uint", hFontOld) DllCall("ReleaseDC", "Uint", hLB, "Uint", hDCListBox) ;Adjust the horizontal bar using LB_SETHORIZONTALEXTENT SendMessage 404, dwMaxExtent, 0,, ahk_id %hLB% } GetListBoxItem(hLB, i) { ;Get length of item. 394 = LB_GETTEXTLEN SendMessage 394, %i%, 0,, ahk_id %hLB% ;Check for error If (ErrorLevel = 0xFFFFFFFF) Return "" ;Prepare variable VarSetCapacity(sText, ErrorLevel, 0) ;Retrieve item. 393 = LB_GETTEXT SendMessage 393, %i%, &sText,, ahk_id %hLB% ;Check for error If (ErrorLevel = 0xFFFFFFFF) Return "" ;Done Return sText } ;--------------------\ ;Scintilla Functions | ;--------------------/ Sci_GetText(hSci) { ;Used constants SCI_GETLENGTH := 2006 SCI_GETTEXT := 2182 ;Retrieve text length SendMessage SCI_GETLENGTH, 0, 0,, ahk_id %hSci% iLength := ErrorLevel ;Open remote buffer (add 1 for 0 at the end of the string) RemoteBuf_Open(hBuf, hSci, iLength + 1) ;Fill buffer with text SendMessage SCI_GETTEXT, iLength + 1, RemoteBuf_Get(hBuf),, ahk_id %hSci% ;Read buffer VarSetCapacity(sText, iLength) RemoteBuf_Read(hBuf, sText, iLength + 1) ;We're done with the remote buffer RemoteBuf_Close(hBuf) Return sText } Sci_GetSelText(hSci) { ;Used constants SCI_GETSELTEXT := 2161 ;Get length SendMessage SCI_GETSELTEXT, 0, 0,, ahk_id %hSci% iLength := ErrorLevel ;Check if it's none If (iLength = 1) Return "" ;Open remote buffer RemoteBuf_Open(hBuf, hSci, iLength) ;Fill buffer SendMessage, SCI_GETSELTEXT, 0, RemoteBuf_Get(hBuf),, ahk_id %hSci% ;Prep var VarSetCapacity(sText, iLength) RemoteBuf_Read(hBuf, sText, iLength) ;Done RemoteBuf_Close(hBuf) Return sText } LineFromPos(pos) { Global ;SCI_LINEFROMPOSITION SendMessage 2166, pos - 1, 0,, ahk_id %hSci% Return ErrorLevel + 1 } ShowLine(line) { Global ;Get the first visible line SendMessage, 2152, 0, 0,, ahk_id %hSci% If (ErrorLevel < line - 1) { ;Get the number of lines on screen. SCI_LINESONSCREEN SendMessage, 2370, 0, 0,, ahk_id %hSci% ;Go to the line wanted + lines on screen. SCI_GOTOLINE SendMessage, 2024, line - 1 + ErrorLevel, 0,, ahk_id %hSci% } SendMessage, 2024, line - 1, 0,, ahk_id %hSci% }