Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

TillaGoto - Go to functions, labels & hks in your script


  • Please log in to reply
135 replies to this topic
TheGood
  • Members
  • 589 posts
  • Last active: Mar 22 2014 03:22 PM
  • Joined: 30 Jul 2007
In the same vein as Active Goto, TillaGoto is a script that shows you a list of all the functions, labels and hotkeys in the currently open script (and even #Include files and library files) and allows you to go to any of them.

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.

Posted Image

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


fincs
  • Moderators
  • 1662 posts
  • Last active:
  • Joined: 05 May 2007
Great work! I'm happily willing to integrate this into SciTE4AutoHotkey, if you want ;)
Also, if you want to support SciTE(4AutoHotkey), use "ahk_class SciTEWindow", please :)

TheGood
  • Members
  • 589 posts
  • Last active: Mar 22 2014 03:22 PM
  • Joined: 30 Jul 2007
Glad you like it! :D
You can use whatever you want for sActiveWindow. It will be processed through WinActive(). So if it'll work there, you can use it! :D

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
TheGood, you've got my vote :), I was trying to do that trough Lexikos lowlevel functions, but there I had the problem whenever syntax was wrong it does not work and functions that are never called are not listed.

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
}


TheGood
  • Members
  • 589 posts
  • Last active: Mar 22 2014 03:22 PM
  • Joined: 30 Jul 2007

TheGood, you've got my vote :)

Thanks! :D
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).

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 :!:

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.

- list hotkeys?

Good idea also. That one will be a tad harder, but shouldn't take long.

- update position of GUI when SciTE is resized/moved

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.

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.

fincs
  • Moderators
  • 1662 posts
  • Last active:
  • Joined: 05 May 2007
I've found a bug:
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
	}
}


HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008

TheGood, you've got my vote :)

Thanks! :D
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).

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.

- update position of GUI when SciTE is resized/moved

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.

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?!

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
How about following :idea:
- 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?

TheGood
  • Members
  • 589 posts
  • Last active: Mar 22 2014 03:22 PM
  • Joined: 30 Jul 2007
OK, first of all thanks fincs for the bug you found. I updated the post with your fix. :D
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:

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".

Also, HotkeyIt, I'm doing this at work right now but I'll reply to the other features you suggested tonight. :D

TheGood
  • Members
  • 589 posts
  • Last active: Mar 22 2014 03:22 PM
  • Joined: 30 Jul 2007

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.

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.

- Hide/Set AlwaysOnTop Off when another window gets on top of SciTE!

The GUI now simply hides when selecting a window other than an editor

- Gui,Margin,0,0 (Looks nicer I think)

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.

- For SciTE, Instead of hotkey, it should start up directly showing list and exits (exitapp) on hide/close

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.

- Leave column header for sorting purpose and add second column displaying line number

Maybe. You need to convince me lol. Why would you need the line number also shown? It seems like distracting information to me.

- Display parameters in functions. what do you think?

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?

- Dock to top right corner?

Yeah, I'm thinking moving it too actually. try this for lines 497 and tell me what you think:
;Calculate location and show it
    iX += iW - iGUIWidth - 30    ; - 50
    iY += 30 ;110

- Optional transparency for GUI?

Yes, I definitely agree! I'll add it in the next release.

TheGood
  • Members
  • 589 posts
  • Last active: Mar 22 2014 03:22 PM
  • Joined: 30 Jul 2007
OK I updated the script. Let me know what you guys think.

- 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



HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Thanks for changes, that was quick :)

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]


TheGood
  • Members
  • 589 posts
  • Last active: Mar 22 2014 03:22 PM
  • Joined: 30 Jul 2007

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.

TheGood
  • Members
  • 589 posts
  • Last active: Mar 22 2014 03:22 PM
  • Joined: 30 Jul 2007
Added Quick Mode (as per HotkeyIt's suggestion). It's actually pretty fast!
bQuickMode := False ;Set to True to make TillaGoto go straight to showing the GUI and exit on close.


HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Many thanks for the QuickMode :)
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%
}