[Script] AutocompleteEditorText - using text from editor/active-window/last-window

Post your working scripts, libraries and tools for AHK v1.1 and older
brutus_skywalker
Posts: 175
Joined: 24 Dec 2016, 13:16
Location: Antarctica

[Script] AutocompleteEditorText - using text from editor/active-window/last-window

13 May 2018, 04:16

You know how notepad++ can autocomplete text using other text in the window, this script does exactly the same thing,except across multiple editors or even unsupported windows such as a browser using text from last supported window, NOTE: for the later to work autocompleteAllWindows must be set to true,which is the default.

Configuration Options:
  • Trigger Length
    One Press Auto-completion (Toggle) *default:TRUE Enter autocompletes+newline, Tab autocompletes+space
    Process Exclusion
    Window Title Exclusion
    All/Compatible-Only Window Auto-completion (Toggle)
    Case Correction (Toggle)
    Case Ranking of Suggestions (Toggle)
    Remove Punctuation from matches (Toggle) *default: FALSE


Interaction is via Up/Down/Enter/Tab, the later two behaving differently based on configuration,see above.


Merci A_AhkUser for insight,suggestive corrections and great library which motivated me to rewrite my tooltip version, now at the bottom of post.

Let me know about any bugs. :D


Legacy toolTip version in my OP can be found at the bottom.

EDIT: Saturday, May 26, 2018 16:42
  • Full Rewrite.
EDIT: Tuesday, May 29, 2018
Added:
  • Support for unknown keyboard layouts. Working On It(Experimental).

    A number of configuration options, see above.

    More endkeys; Left,Right,LButton,RButton

    A lot of little things under the hood....
Fixed:
  • Use ByRef where necessary for performance. Thx @A_AhkUser for pointing it out.
EDIT: Wednesday, May 30, 2018
Fixed: Issues with #If not properly working, it was a conflict with another script on my end.

EDIT: Thursday, May 31, 2018
Fixed: ListBox error message.
Fixed: ListBox flickering on window change.

EDIT: Saturday, June 02, 2018
Just added version information, and optimised some of the logic.


Edit: Monday, June 04, 2018
v1.1
Added: sanityCheck to ensure script behaviour is as predictable as possible.
Fixed: ListBox being generated beyond the edges of the screen when typing at the edges of the display.
Fixed: Endkey backspace hotkey thread no longer interrupted, ensures backspace works as expected.
Fixed: Listbox hotkey threads no longer interrupted, ensures Tab/Enter work as expected.
Added: A few more endkeys to cover more use cases,such as Numpad Arrows.


Code: Select all

;	v1.1
;
;	brutus_skywalker
;
; AutoHotkey:	 v1.1.26.01 Unicode 32-bit (Installed)
; Language:		 English
; OS:  			 WIN_7 64-bit Service Pack 1 v6.1.7601 (WIN32_NT)  , 6050.68MB RAM
; Date:          Sunday, May 27, 2018 12:34
;
;https://autohotkey.com/boards/viewtopic.php?f=6&t=48915

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
#SingleInstance, Force
; #NoTrayIcon


;==================================== CONFIGURATION START =================================================

;Number of input strings to trigger AutoCompletion Suggestions
triggerLength := 2

;processes to exclude
excludedProcessesList = notepad++.exe,sublime_text.exe		;these examples have their own autocompletion...

;if active title contains any comma delimited strings below, then autocompletion will be deactivated for that window
excludedTitles = CodeQuickTester,Pulover's Macro Creator,GoToTilla,GoTo

;toggle one press autocomplete,i.e 'Tab' autocompletes with a space and 'Enter' will goto newline after autocomplete
onePressAutocomplete := True

;If autocomplete is enabled for all windows, text from last supported window will be used for autocompletion in an unsupported window where active input is detected
autocompleteAllWindows := True	;this refers to windows from which a wordlist can not be retrieved

;Input string will be replaced by suggested string,instead of completing missing strings. If true it's slower as input strings need to be removed before autocompletion.
caseCorrection := false

;Suggestions are ranked by Case,relative to input case.
caseSensitiveSuggesstions := True

;remove punctuation from matches.
removePunctuationFromMatches := false

;===================================== CONFIGURATION END ==================================================

;get version of this script  -  First Line of this script is reserved as a version header, so don't remove it!
If !A_IsCompiled
	FileReadLine, thisScriptVersion, %A_ScriptFullPath%, 1
StringTrimLeft, thisScriptVersion, thisScriptVersion, 2
	
trayMsg =
(
Trigger Length:		%triggerLength%

One Press Autocomplete:	%onePressAutocomplete%

Autocomplete All Win.:	%autocompleteAllWindows%

Case Correction:		%caseCorrection%

Case Rank:		%caseSensitiveSuggesstions%

Remove Punctuations:	%removePunctuationFromMatches%

Excluded Processes:
%excludedProcessesList%
)
TrayTip, AutocompleteExistingText [@brutus_skywalker] %thisScriptVersion%, % StrReplace(StrReplace(trayMsg, "	1", "	True"), "	0", "	False") , , 1








;============================================================= AutoExecute Start ==================================================

;add excluded processes defined above to group
Loop, Parse, excludedProcessesList, `,
	GroupAdd, excludedProcesses, ahk_exe %A_LoopField%

If caseSensitiveSuggesstions
	StringCaseSense, On

DetectHiddenWindows, On
#MaxThreadsBuffer On
#MaxThreadsPerHotkey 1	
Gosub, initialiseInputHotkeys
Gosub, updateWordList
SetTimer, updateWordList, 2500
SetTimer, autocompletePermissions, 100
SetTimer, sanityCheck, 250	;monitors observed anomalies that occur as a result of unpredictable user behaviour or possible anomalous script behaviour.

Loop
	GetInputString(word)		;accepts unknown characters not handled by hotkeys, this method isn't primary as it misses inputs in a large enough script.

	
;==================================================================================================================================	
	
	
	
	
	
	


;------------------------------------------------------------------------------------
/*
NOTE: These are script anomalies that could happen due to variances in speed of user input. Adjusting the script to a specific input speed range,
is unreasonable,and it only results in either making it more or less responsive but not both across the wide range of user input speed, as such,
these simple checks serve to maintain that balance by correcting anomalies that occur at either input speed extreme.
*/
;------------------------------------------------------------------------------------
sanityCheck:		;checks for observed anomalies to make corrections.
;------------------------------------------------------------------------------------
SetBatchLines, -1
;------------------------------------------------------------------------------------
If (isListBoxActive AND !word){
GoSub, GuiClose
}
;------------------------------------------------------------------------------------
DetectHiddenWindows, off	;only cross check non-hidden windows,as listbox if not visible is always hidden
If (!isListBoxActive AND WinExist(listBoxTitle)){
GoSub, GuiClose
}Else If(isListBoxActive AND !WinExist(listBoxTitle)){
GoSub, GuiClose
}
DetectHiddenWindows, on
;------------------------------------------------------------------------------------
SetBatchLines, 10ms	;default
;------------------------------------------------------------------------------------
Return
;------------------------------------------------------------------------------------


	
;activates and deactivates hotkeys based on the compatibility/eclusion rules of/for active window.
autocompletePermissions:
	If WinActive("ahk_group excludedProcesses"){	;if excluded process,then suspend hotkeys
		Suspend, on
		isSuspended := True
	}Else If(!GetTextFromControl() AND !autocompleteAllWindows){	;if unsupported window and autocompleteAllWindows is disabled
		Suspend, on
		isSuspended := True
	}Else If WinTitleContains(excludedTitles){
		Suspend, on
		isSuspended := True
	}Else{
		Suspend, off
		isSuspended := False
	}
Return


;'word' is the global variable that corresponds to currently being typed string
updateListBox:
SetBatchLines, -1
theseWords := WordsStartWithString(word,wordList,triggerLength)		;search current wordlist for matches of already typed string & output wordList, with second param containing minimum strLen to trigger match.
;ToolTip, % A_Index "`n" theseWords "`n" word, 0 ,0, 2
; ToolTip, % LB  "-" StringsToPut(word, LB) "-" word
If theseWords
	CreateUpdateListBox( theseWords )							;use the wordlist from search result to generate an interactive ListBox
Else{
	Gui, Show, Hide, %listBoxTitle%						;if no matches for currently input string Destroy listbox
	isListBoxActive := false
	}
SetBatchLines, 10ms ;default
return



;Create transparent Hotkeys for all relevant keys
initialiseInputHotkeys:
; Normal keys.
For Index, Key in StrSplit("abcdefghijklmnopqrstuvwxyz01234567890") {
	Hotkey, % "~" Key, parseInputString
	Hotkey, % "~+" Key, parseInputStringUpper
}
;End keys
endKeyList = Enter Tab Space `. `; `, `: `? `! `' `" `( `) `] `{ `} BackSpace Esc LButton RButton Left Right Pgdn Pgup End Home Ins Del NumpadLeft NumpadRight NumpadUp NumpadDown NumpadPgdn NumpadPgup NumpadHome NumpadEnd NumpadIns NumpadDel
For Index, Key in StrSplit(endKeyList, A_Space)
	Hotkey, % "~" Key, endKeys
return



parseInputString:
StringTrimLeft, pressedHotkey, A_ThisHotkey, 1	;To remove '~' from A_ThisHotkey
If (GetKeyState("Capslock", "T")) ; If Capslock is on, then send the capped key
	StringUpper, pressedHotkey, pressedHotkey
word .= pressedHotkey
Gosub, updateListBox
return

parseInputStringUpper:
StringTrimLeft, pressedHotkey, A_ThisHotkey, 2	;To remove '~' and '+' from A_ThisHotkey
StringUpper, pressedHotkey, pressedHotkey
word .= pressedHotkey
Gosub, updateListBox
return

endKeys:
Critical
;ToolTip, % A_ThisHotkey, 0,0,2		;debug ToolTip
If (A_ThisHotkey = "~BackSpace"){
	StringTrimRight, word, word, 1
	Gosub, updateListBox
	return
}
Critical, off
Gosub, GuiClose
Gosub, updateListBox
return





;=====================================
;Dynamic Wordlist Parsing label
;=====================================
updateWordList:
	SetBatchLines, -1	;go as fast as possible to reduce update delay
	thisEditorText := GetTextFromControl()

	If !thisEditorText{		;to preseve wordlist from last supported window for windows from which a wordlist can not be retrieved.
		SetBatchLines, 10ms	;default speed
		supportedWindow := false	;signifies active window has no supported edit control
		Return
	}
	supportedWindow:=true
	
	;don't rebuild word list if text content of window hasn't changed
	IfEqual, thisEditorText, %lastEditorText%
		{
		SetBatchLines, 10ms	;default speed
		Return
		}

	SetTimer, updateWordList, off
	While (GetAllKeysPressed() AND A_TimeIdlePhysical < 1000 OR isListBoxActive){		;wait until user is done typing before updating the word list further
		Sleep, 1000
		}
	SetTimer, updateWordList, on


	;Get Wordlist
	this_WordList := AlphaSortList(GetActiveWinWordList())		;get & sort worldlist from active window text
	If (!this_WordList){	;if current window has no wordlist,retain old one.
		SetBatchLines, 10ms	;default speed
		Return
		}
	
;	ToolTip, % this_WordList, 400, 0, 3
	WordList := this_WordList
;	SoundBeep
	

	lastEditorText := thisEditorText
	SetBatchLines, 10ms	;default speed
	Return









;=====================================
;ListBox control and interaction labels
;=====================================
;corresponds to an invisible button that allows interaction using Up/Down/Enter keys with listbox. Updates on pressing Enter.
ButtonInput:
Gui, Submit, NoHide
;ToolTip, %LB%
GoSub, GuiClose
Return

;updates with every change in selection of listbox element, 'LB' contains current selection.
CurrentSelection:
Gui, Submit, NoHide
; ToolTip, % LB  "-" StringsToPut(word, LB) "-" word
Return

;moves listboxes to caret position with offset so no occlusion occurs,and destroys lisbox when window changes.
FollowCaret:
;ensures listbox is positioned consistently & predictably
CoordMode, caret, Screen
;move ListBox to caret position with an offset so it doesn't occlude text on caret position


;Keep ListBox from being generated outside the edges of the display...
; ToolTip, % W "`n" H "`n" A_CaretX + 20 "`n" A_CaretY + 25
If( A_CaretX + 20 + W < A_ScreenWidth AND A_CaretY + 25 + H < A_ScreenHeight){
WinMove, %listBoxTitle%, , % A_CaretX + 20, % A_CaretY + 25
	}
Else{
	If(A_CaretX + 20 + W > A_ScreenWidth){		;if width is beyond display width
		xMove := A_CaretX - 20 - W
	}Else
		xMove := A_CaretX + 20
	If(A_CaretY + 25 + H > A_ScreenHeight){		;if height is beyond display width
		yMove := A_CaretY - 25 - H
	}Else
		yMove := A_CaretY + 25
		
	WinMove, %listBoxTitle%, , % xMove, % yMove
	}


If (!A_CaretX OR WinChanged()){	;if no caret Destroy Gui
Gosub, GuiClose
}
If WinChanged()
	Gosub, updateWordList
Return


GuiClose:
GuiEscape:
word := ""
Gui, Show, Hide, %listBoxTitle%
isListBoxActive := false
Return



;List Box Interaction Hotkeys - context sensitive to listbox,and send inputs sent directly to the listbox.
#If isListBoxActive	;if listbox exists and suggestions are active
Up::
Down::
Enter::
Tab::
Esc::
SetBatchLines, -1
Critical
IfEqual, A_ThisHotkey, Up
	ControlSend, , {Up}, % listBoxTitle
IfEqual, A_ThisHotkey, Down
	ControlSend, , {Down}, % listBoxTitle
IfEqual, A_ThisHotkey, Enter
{
	ControlSend, , {Enter}, % listBoxTitle
	If !caseCorrection{
		If onePressAutocomplete
			Put(StringsToPut(word,LB) . "`n")		;input remaining strings to Autocomplete and newline -as enter was pressed
		Else
			Put(StringsToPut(word,LB))		
	}Else{
		inputStrLength := StrLen(word)
		Send {BackSpace %inputStrLength%}	;backspace remove already input strings
		If onePressAutocomplete
			Put(LB . "`n")		;input current selection in full.
		Else
			Put(LB)		
	}
	Gosub, GuiClose
}
IfEqual, A_ThisHotkey, Tab
{
	ControlSend, , {Enter}, % listBoxTitle
	If !caseCorrection{
		If onePressAutocomplete
			Put(StringsToPut(word,LB) . A_Space)		;input remaining strings to Autocomplete and space -as tab was pressed
		Else
			Put(StringsToPut(word,LB))		
	}Else{
		inputStrLength := StrLen(word)
		Send {BackSpace %inputStrLength%}	;backspace remove already input strings
		If onePressAutocomplete
			Put(LB . A_Space)		;input current selection in full.
		Else
			Put(LB)		
	}
	Gosub, GuiClose
}
IfEqual, A_ThisHotkey, Esc
	Gosub, GuiClose
Critical, off
SetBatchLines, 10ms	;default
Return
#If










;Autocomplete Search Engine
WordsStartWithString(string,ByRef wordList, stringLenToMatch:=1){
	If ( StrLen(string) >= stringLenToMatch AND StrLen(string)){
		Loop, Parse, wordList, `n
			IfEqual, string, % SubStr(A_LoopField, 1, StrLen(string))	;if current looped word starts with the string,then it's a match.
				matches .= A_LoopField "`n"
		IfEqual, A_StringCaseSense, off	;return matches if only a case insensitive matching was used,as it has all possible matches.
			{
			RemoveDuplicatesInList(matches, "`n")
			Return, matches
			}
		Else	;append matches that were passed up for case insensitivity to above matches.
			{
			StringCaseSense, off
			Loop, Parse, wordList, `n
				IfEqual, string, % SubStr(A_LoopField, 1, StrLen(string))	;if current looped word starts with the string,then it's a match.
					{
					StringCaseSense, on
					matches .= A_LoopField "`n"
					StringCaseSense, off
					}
			StringCaseSense, on
			
			RemoveDuplicatesInList(matches, "`n")
			
			Return, matches
			}
	}
}


;MsgBox, % StringsToPut("ar", "archemedis") ;will return 'chemedis'
StringsToPut(string, match){	;takes strings already typed, actively matched string & determines what strings to append to complete typed strings.
	Return, % SubStr(match, StrLen(string)+1, StrLen(match))
}



;Created listbox will dynamically resize itself to the width & height of provided WordList & Up/Down/Enter selection of list.
CreateUpdateListBox(WordList){		;Verbose Commentary as this was my first ListBox, might be helpful to others as well....
	static
	Global listBoxTitle, isListBoxActive, W, H
	listBoxTitle = WordListGUI
	fontSize = 12
	If guiInitialised{	;if listbox already created,update it
		;leave off the first | to add to listbox instead of replacing It's contents.
		;Consider chosen delimiter here,i.e if Delimiter is line feed then add `n before new list to replace old list.
		GuiControl,,LB,`n%WordList%
		;get dimensions for updated list
		W := LB_EX_CalcWidth(HLB)
		H := LB_EX_CalcHeight(HLB)
		;select the first entry on updated listBox
		Control, Choose, 1, , %listBoxTitle%
		;scale updated list to number of elements
		GuiControl, Move, LB, h%H% w%W%
		Gui, +AlwaysOnTop
		Gui, Show, AutoSize NoActivate, %listBoxTitle%
		isListBoxActive := true
		Gosub, FollowCaret									;update ListBox position to caret position
		Return
	}
	
	guiInitialised:=True	;signifies gui has already been created and need only be updated in future function calls.

	;set font & size, ultimately determines gui size
	Gui, Font, s%fontSize%, Courier New
	;to set Delimiter used by GUI, default is pipe '|', any entry followed by double pipes will be default selection, 'red|blue||green', -blue will be default selection.
	Gui, +Delimiter`n
	;vLB -has current listbox selection, gCurrentSelection -triggers the label on new selection, Choose1 sets first entry as default selection.
	;Extra Note: AltSubmit will Return Index of current listbox selection, instead of currently selected text.
	;hwndHLB saves the handle to the listbox to a variable: 'HLB', for use with justme's functions.
	Gui, Add, ListBox,x0 y0 -vscroll -hscroll Choose1 hwndHLB vLB gCurrentSelection, %WordList%
	;determine suitable width and height for current list
	W := LB_EX_CalcWidth(HLB)
	H := LB_EX_CalcHeight(HLB)
	;input button for Up/Down/Enter interaction, it triggers gButtonInput when Enter is pressed or DoubleClick is detected.
	Gui, Add, Button,x0 y0 Default Hidden gButtonInput
	;to remove the excess gui margins to the right and bottom
	Gui, Margin, 0, 0
	;...Remove titlebar & borders, ...Set as AlwaysOnTop, Set as LastFound window for succeding commands that act on a GUI/Window
	Gui, -Caption +ToolWindow +AlwaysOnTop +LastFound -SysMenu
	;set gui dimensions
	GuiControl, Move, LB, w%W% h%H%
	;	Gui, Show
	Gui,Show, AutoSize NoActivate X%A_ScreenWidth% Y%A_ScreenHeight%, %listBoxTitle%	;create gui offscreen, to avoid flickering and such.
	isListBoxActive := true
	
	;initialise current window as last window when checking for window change
	WinChanged()
	;initialise caret following, to place listbox at current caret position
	Gosub, FollowCaret
	SetTimer, FollowCaret, 100
	lastWordList := WordList
}

WinChanged(){
	static lastHwnd
	If ( WinExist("A") != lastHwnd ){
		lastHwnd := WinExist("A")
		Return True
	}
}

Put(ByRef string){		;as an alternative to send,to instantly place a string. Using ByRef to get as best a performance as possible however negligible.
Global supportedWindow
	ClipSaved := ClipboardAll
	clipboard := ""
	clipboard := string
	clipwait
	BlockInput, on
	If supportedWindow
		Send ^v
	Else
		Send, % string
	BlockInput, off
	Clipboard := ClipSaved
}


WinTitleContains(titles){	;Returns true if active title contains strings defined in comma delimited param.
	WinGetActiveTitle, activeTitle
    Loop, Parse, titles, `,
        IfInString, activeTitle, %A_LoopField%
			Return, A_LoopField
}







;ListBox functions excerpt from the LBEX library by 'justme'

LB_EX_CalcWidth(HLB) { ; Calculates the width of the list box needed to show the whole content
	; HLB - Handle to the ListBox
	MaxW := 0
	ControlGet, Items, List, , , % "ahk_id " . HLB
	SendMessage, 0x0031, 0, 0, , % "ahk_id " . HLB ; WM_GETFONT
	HFONT := ErrorLevel
	HDC := DllCall("User32.dll\GetDC", "Ptr", HLB, "UPtr")
	DllCall("Gdi32.dll\SelectObject", "Ptr", HDC, "Ptr", HFONT)
	VarSetCapacity(SIZE, 8, 0)
	Loop, Parse, Items, `n
	{
		Txt := A_LoopField
		DllCall("Gdi32.dll\GetTextExtentPoint32", "Ptr", HDC, "Ptr", &Txt, "Int", StrLen(Txt), "Ptr", &Size)
		If (W := NumGet(SIZE, 0, "Int")) > MaxW
			MaxW := W
	}
	DllCall("User32.dll\ReleaseDC", "Ptr", HLB, "Ptr", HDC)
	Return MaxW + 8 ; + 8 for the margins
}
LB_EX_CalcHeight(HLB) { ; Calculates the height of the list box needed to show the whole content
	; HLB - Handle to the ListBox
	Static LB_GETITEMHEIGHT := 0x01A1
	Static LB_GETCOUNT := 0x018B
	SendMessage, % LB_GETITEMHEIGHT, 0, 0, , % "ahk_id " . HLB
	H := ErrorLevel
	SendMessage, % LB_GETCOUNT, 0, 0, , % "ahk_id " . HLB
	Return (H * ErrorLevel) + 8 ; + 8 for the margins
}



















;Dynamic WordList Parsing functions
GetActiveWinWordList(){
Static
	text := GetTextFromControl()
	If !text	;if no text can be retrieved from a control get all text in window
		WinGetText, text, A
	;rebuild WordList only if text in editor has changed
	IfEqual, text, %lastText%
		Return lastWordList
	lastText := text

	;redact all punctuations if options was set to true
	Global removePunctuationFromMatches
	If (removePunctuationFromMatches){	;doing it this way because i couldn't figure it out with RegEx
	punctuation = `. `; `, `: `? `! `' `" `( `) `[ `] `{ `} `/ `\ `< `>
	Loop, Parse, punctuation, %A_Space%
		StringReplace, text, text, %A_LoopField%, `n, ALL
	}	
	
	StringReplace, text, text, `,, `n, ALL
	StringReplace, text, text, `r`n, `n, ALL
	StringReplace, text, text, %A_Space%, `n, ALL
	StringReplace, text, text, %A_Tab%, `n, ALL
	;remove white spaces
	wordListText := RegExReplace(text , "(^|\R)\K\s+")
	;rebuild wordlist using only strings longer than 3 characters
	Wordlist:=""
	Loop, Parse, wordListText, `n
		If StrLen(A_LoopField) >= 3		;strings of over 3 characters
			IfNotInString, Wordlist, %A_Space%%A_LoopField%%A_Space%	;avoid repeated strings
				Wordlist.= " " A_LoopField " " "`n"	;add spaces on either side so that words in other word can easily be added to list
	StringReplace, Wordlist, Wordlist, %A_Space%, , ALL ;spaces are no longer needed as list is done building
	lastWordList := WordList
	Return WordList
}




GetTextFromControl(){
	WinGet, controlList, ControlList, A
	WinGet, ProcName , ProcessName, A
    if regexmatch(ProcName, "i)(?:Sc(?:\d+|iTE))|(?:notepad(?:\+\+|2)).exe") 
        ControlGetText, text ,Scintilla1, A
	else if InStr(controlList, "Edit3")
		ControlGetText, text ,Edit3, A
	else if InStr(controlList, "Edit1")
		ControlGetText, text ,Edit1, A
	else if InStr(controlList, "TConTEXTSynEdit1")
		ControlGetText, text ,TConTEXTSynEdit1, A
	else if InStr(controlList, "RICHEDIT50W1")
		ControlGetText, text ,RICHEDIT50W1, A
	else if InStr(controlList, "Scintilla1")
		ControlGetText, text ,Scintilla1, A
	return (text ? text : "")
}




AlphaSortList(ByRef list){
StringSplit, line, list, `n,`r
Loop, parse, list, `n, `r
	data1 .= (data1?"`n":"") A_LoopField "|" A_Index
data1 := RegExReplace(data1, "[ `,“”\(\)\/`:]")
sort, data1
loop, parse, data1, `n,`r
	l := RegExReplace(A_LoopField, ".*\|")	, out .= (out?"`n":"") line%l%
Return out
}


; returns a variable containing names of all keys pressed down currently.
; Pass "L" (default) to check only the logical state of the keys, and "P" to check the physical state
GetAllKeysPressed(mode = "L") {
SetBatchLines, -1
		keys = ``|1|2|3|4|5|6|7|8|9|0|-|=|[|]\|;|'|,|.|/|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|Esc|Tab|CapsLock|LShift|RShift|LCtrl|RCtrl|LWin|RWin|LAlt|RAlt|Space|AppsKey|Up|Down|Left|Right|Enter|BackSpace|Delete|Home|End|PGUP|PGDN|PrintScreen|ScrollLock|Pause|Insert|NumLock|F1|F2|F3|F4|F5|F6|F7|F8|F9|F10|F11|F12|F13|F14|F15|F16|F17|F18|F19|F20
	; '|' isn't a key itself (with '\' being the "actual" key), so okay to use is as a delimiter
	Loop Parse, keys, |
	{		
		key = %A_LoopField%				
		isDown :=  GetKeyState(key, mode)
		if isDown
			pressed .= key	
	}   
SetBatchLines, 10ms
	return pressed
}



RemoveDuplicatesInList(ByRef list, delimiter){
caseSenseState := A_StringCaseSense
StringCaseSense, on
;create a padded list so that words in other words aren't matched
;the logic is, pseduocode: IfInString, " HelloWorld "," Hello " will not return true,
;where as IfInString, "HelloWorld", "Hello" will return true. This method is more effective in lists.
Loop, Parse, list, %delimiter%
	paddedList .= A_Space . A_LoopField . A_Space . "`n"
;rebuild list minus duplicates
list := ""
Loop, Parse, paddedList, `n
	IfNotInString, list, %A_LoopField%
		list .= A_LoopField "`n"
StringReplace, list, list, %A_Space%, , ALL	;remove all space, as padding is no longer needed
StringCaseSense, %caseSenseState%
}




;============================================================================================
/*
For Broader Keyboard Compatibility, an alternate Input capture scheme that appends to the main input string.
It only accepts inputs that aren't covered by hotkeys to prevent duplication of inputs,
*/
;============================================================================================





GetInputString(ByRef word){
	hotkeys = a A b B c C d D e E f F g G h H i I j J k K l L m M n N o O p P q Q r R s S t T u U v V w W x X y Y z Z 0 1 2 3 4 5 6 7 8 9 0 Enter Tab Space `. `; `, `: `? `! `' `" `( `) `] `{ `} BackSpace Esc
	;Get one key at a time
	Input, chr, L1 V,{enter}{space}.;`,:¿?¡!'"()]{}{bs}{esc}
	SetBatchLines, -1
	Loop, Parse, hotkeys, %A_Space%
		IfEqual, chr, %A_LoopField%
			atLeastOneHotKeyMatches := true
	
	Global isSuspended
	If (!atLeastOneHotKeyMatches AND !isSuspended){		;if no hotkey matches currently input character add it to currently input word
		word = %word%%chr%
		}
	SetBatchLines, 10ms	;default
}





Below are the components that comprise the above script, i'm posting them such that others may have an easier start in similar scripts or just code to cannibalise if you so wish...cheers.
Spoiler



Legacy Tooltip version, in my OP is also down here for any one who might want it,as it's less intrusive being a tooltip and all. I Run both personally, i run the tooltip version on all my Virtual Machines,as it works across the board with no hiccups.

Based on variations on THIS post of rajat's Intellisense script modifications.

; DOUBLE Press 1 to 0 keys to autocomplete the word upon suggestion
; (0 will match suggestion 10)

Code: Select all

/*
CONFIGURATION NOTE:
Tooltip position is defined on line 163.
Input method is defined on line 330,It's currently an alternation between Clipboard"using the Put() function" or sending it raw based on context.
Update rate defined on line 53.
*/


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

#SingleInstance, force 


SetKeyDelay, 0
CoordMode, ToolTip, Relative
AutoTrim, Off

;_______________________________________

;    CONFIGURATIONS

; Editor Window Recognition
; (make it blank to make the script seek all windows)

ETitle = 

;Minimum word length to make a guess
WLen = 3
keyagain=
key=
clearword=1
;Gosub,clearallvars   ; clean vars from start

; Press 1 to 0 keys to autocomplete the word upon suggestion
; (0 will match suggestion 10) 
;_______________________________________



Wordlist=	;for debug/testing
(
this
is
a
baseline
wordlist
)


SetTimer, updateWordList, 10000
Gosub, updateWordList


;reads all words from var
Loop, Parse,  Wordlist, `n
{
   tosend = %a_loopfield%
   cmd%a_index% = %toSend%
}

SetTimer, Winchanged, 100

Loop
{
   ;Editor window check
    WinGetActiveTitle, ATitle
    WinGet, A_id, ID, %ATitle%
    IfNotInString, ATitle, %ETitle%
    {
      ToolTip
      Setenv, Word,
      sleep, 500
      Continue
  }
   
   ;Get one key at a time
   Input, chr, L1 V,{enter}{space}.;`,:¿?¡!'"()]{}{bs}{esc}
   EndKey = %errorlevel%
   ; If active window has different window ID from before the input, blank word
   ; (well, assign the number pressed to the word)
   WinGetActiveTitle, ATitle
   WinGet, A_id2, ID, %ATitle%
   IfNotEqual, A_id, %A_id2%
   {
      Gosub,clearallvars
      Setenv, Word, %chr%
      Continue
   }
   
   ;Blanks word reserve
   ifequal, EndKey, Endkey:Enter, Gosub,clearallvars
   ifequal, EndKey, Endkey:Escape, Gosub,clearallvars
   ifequal, EndKey, Endkey:Space, Gosub,clearallvars
   ifequal, EndKey, Endkey:`,, Gosub,clearallvars
   ifequal, EndKey, Endkey:., Gosub,clearallvars
   ifequal, EndKey, Endkey:`:, Gosub,clearallvars
   ifequal, EndKey, Endkey:`;, Gosub,clearallvars
   ifequal, EndKey, Endkey:!, Gosub,clearallvars
   ifequal, EndKey, Endkey:¡, Gosub,clearallvars
   ifequal, EndKey, Endkey:?, Gosub,clearallvars
   ifequal, EndKey, Endkey:¿, Gosub,clearallvars
   ifequal, EndKey, Endkey:", Gosub,clearallvars		;" comment to normalise syntax highliting
   ifequal, EndKey, Endkey:', Gosub,clearallvars
   ifequal, EndKey, Endkey:(, Gosub,clearallvars
   ifequal, EndKey, Endkey:), Gosub,clearallvars
;   ifequal, EndKey, Endkey:[, Gosub,clearallvars  -- this was causing problem number 1
   ifequal, EndKey, Endkey:], Gosub,clearallvars
   ifequal, EndKey, Endkey:{, Gosub,clearallvars
   ifequal, EndKey, Endkey:}, Gosub,clearallvars
   
   ;Backspace clears last letter
   ifequal, EndKey, Endkey:BackSpace, StringTrimRight, Word, Word, 1
   ifnotequal, EndKey, Endkey:BackSpace, Setenv, Word, %word%%chr%
   
   ;Wait till minimum letters
   StringLen, len, Word
   IfLess, len, %wlen%
   {
      ToolTip
      Continue
   }
   
   ;Match part-word with command
   Num =
   Match = 
   singlematch = 0
   number = 0
   Loop
   {
      IfEqual, cmd%a_index%,, Break
      StringLen, chars, Word
      StringLeft, strippedcmd, cmd%a_index%, %chars% 
      StringLeft, strippedword, Word, %chars% 
      ifequal, strippedcmd, %strippedword%
      {
         num = %a_index%
         number ++

         ; Create list of matches
         StringTrimLeft, singlematch, cmd%num%, 0
         match = %match%%number%. %singlematch%`n

         ; Map singlematch with corresponding cmd
         singlematch%number%=cmd%num%

         Continue
      }
   }

   ;If no match then clear Tip
   IfEqual, Num,
   {
      clearword=0
      Gosub,clearallvars
      Continue
   }
   
   ;Show matched command
   StringTrimRight, match, match, 1        ; Get rid of the last linefeed
   IfNotEqual, Word,,ToolTip, %match%, A_CaretX + 200, A_CaretY + 40
} 

; Timed function to detect change of focus (and remove tooltip when changing active window)
Winchanged:
   WinGetActiveTitle, ATitle
   WinGet, A_id3, ID, %ATitle%
   IfNotEqual, A_id, %A_id3%
   {
      ToolTip
	  Gosub, updateWordList			;update word list anytime window changes
   }
   Return
   
; Key definitions for autocomplete (0 to 9)
#MaxThreadsPerHotkey 1
$1::
$2::
$3::
$4::
$5::
$6::
$7::
$8::
$9::
$0::
Gosub, checkword
Return



; If hotkey was pressed, check wether there's a match going on and send it, otherwise send the number(s) typed
checkword:
SetBatchLines, -1
   clearword=1
   Suspend, on    ; Suspend hotkeys so that they don't interfere with the second press
	key := A_ThisHotkey
	StringTrimLeft, key, key, 1
   ; If active window has different window ID from before the input, blank word
   ; (well, assign the number pressed to the word)
   WinGetActiveTitle, ATitle
   WinGet, A_id2, ID, %ATitle%
   IfNotEqual, A_id, %A_id2%
      {
         if key =10
            key = 0
         Send,%key%
         Gosub,clearallvars
         Suspend, off
         Return
      }

   if word=        ; only continue if word is not empty
      {
         if key =10
            key = 0
         Send,%key%
         Setenv, Word, %key%
         clearword=0
         Gosub,clearallvars
         Suspend, off
         Return
      }
   
   ifequal, singlematch%key%,   ; only continue singlematch is not empty
      {
         if key =10
            key = 0
         Send,%key%
         Setenv, Word, %word%%key%
         clearword=0
         Gosub,clearallvars
         Suspend, off
         Return
      }

   ; 2nd press to confirm replacement
   Input, keyagain, L1 I T0.5, 1234567890

;   msgbox, ErrorLevel=%ErrorLevel%   ; UNCOMMENT FOR TESTING 2ND PROBLEM DISCUSSED IN POST

   ; If there is a timeout, abort replacement, send key and return
   IfEqual, ErrorLevel, Timeout
   {
      if key =10
         key = 0
      Send, %key%
      Setenv, Word, %word%%key%
      clearword=0
      Gosub,clearallvars
      Suspend, off
      Return
   }
   
      
   ; Make sure it's an EndKey, otherwise abort replacement, send key and return
   IfNotInString, ErrorLevel, EndKey:
   {
      if key =10
         key = 0
      Send, %key%%keyagain%
      Setenv, Word, %word%%key%%keyagain%
      clearword=0
      Gosub,clearallvars
      Suspend, off
      Return
   }

   ; If the 2nd key is NOT the same 1st trigger key, abort replacement and send keys
   if key =10
      key = 0
   IfNotInString,ErrorLevel, %key%
   {
     StringTrimLeft, keyagain, ErrorLevel, 7
      Send, %key%%keyagain%
      Setenv, Word, %word%%key%%keyagain%
      clearword=0
      Gosub,clearallvars
      Suspend, off
      Return
   }

   ; SEND THE WORD!
   if key =0
      key = 10
   StringTrimLeft, lastone, singlematch%key%, 0 ; This is because i can't get %singlematch%%key% 
   StringTrimLeft, sending, %lastone%, 0        ; to work in this line
   StringLen, len, Word
   Send, {BS %len%}    ; First do the backpaces
   
   If A_CaretX		;if there is a caret then this is an editable window so paste into it
	Put(sending . " ")  ; Paste it instead - clipboard will be backed up
   Else		;if there is no caret send it raw as keystrokes
	SendRaw, %sending%  ; Then send word (Raw because we want the string exactly as in wordlist.txt)
	
   Gosub,clearallvars
   Suspend, off
   SetBatchLines, 10ms
   Return


; This is to blank all vars related to matches, tooltip and (optionally) word
clearallvars:
	SetBatchLines, -1
      Ifequal,clearword,1,Setenv,word,
      ToolTip 
	  ; Clear all singlematches
      Loop, 10
      {
      	singlematch%a_index% =
      }
      sending = 
      key=
      match=
      clearword=1
	SetBatchLines, 10ms	;default
      Return


updateWordList:
	SetBatchLines, -1	;go as fast as possible to reduce update delay
	thisEditorText := GetTextFromControl()

	;don't rebuild word list if text content of window hasn't changed
	IfEqual, thisEditorText, %lastEditorText%
		{
		SetBatchLines, 10ms	;default speed
		Return
		}

	SetTimer, updateWordList, off
	While (GetAllKeysPressed() AND A_TimeIdlePhysical < 5000){		;wait until user is done typing before updating the word list further
		Sleep, 1000
		}
	SetTimer, updateWordList, on


	;Get Wordlist
	this_WordList := AlphaSortList(GetActiveWinWordList())		;get & sort worldlist from active window text
	If (!this_WordList){	;if current window has no wordlist,retain old one.
		SetBatchLines, 10ms	;default speed
		Return
		}

	;rebuild wordlist to vars
	Loop, Parse,  this_WordList, `n
	{
	   tosend = %a_loopfield%
	   cmd%a_index% = %toSend%
	}
	; ToolTip, %WordList%
	; SoundBeep

	lastEditorText := thisEditorText
	SetBatchLines, 10ms	;default speed
	Return



Put(string){		;as an alternative to send,to instantly place a string
	ClipSaved := ClipboardAll
	clipboard := ""
	clipboard := string
	clipwait
	BlockInput, on
	Send ^v
	BlockInput, off
	Clipboard := ClipSaved
}




GetActiveWinWordList(){
	Global lastText
	Global lastWordList
	text := GetTextFromControl()
	If !text	;if no text can be retrieved from a control get all text in window
		WinGetText, text, A
	;rebuild WordList only if text in editor has changed
	IfEqual, text, %lastText%
		Return lastWordList
	lastText := text
	StringReplace, text, text, `,, `n, ALL
	StringReplace, text, text, `r`n, `n, ALL
	StringReplace, text, text, %A_Space%, `n, ALL
	StringReplace, text, text, %A_Tab%, `n, ALL
	;remove white spaces
	wordListText := RegExReplace(text , "(^|\R)\K\s+")
	;rebuild wordlist using only strings longer than 3 characters
	Wordlist:=""
	Loop, Parse, wordListText, `n
		If StrLen(A_LoopField) >= 3		;strings of over 3 characters
			IfNotInString, Wordlist, %A_Space%%A_LoopField%%A_Space%	;avoid repeated strings
				Wordlist.= " " A_LoopField " " "`n"	;add spaces on either side so that words in other word can easily be added to list
	StringReplace, Wordlist, Wordlist, %A_Space%, , ALL ;spaces are no longer needed as list is done building
	lastWordList := WordList
	Return WordList
}




GetTextFromControl(){
	WinGet, controlList, ControlList, A
	WinGet, ProcName , ProcessName, A
    if regexmatch(ProcName, "i)(?:Sc(?:\d+|iTE))|(?:notepad(?:\+\+|2)).exe") 
        ControlGetText, text ,Scintilla1, A
	else if InStr(controlList, "Edit3")
		ControlGetText, text ,Edit3, A
	else if InStr(controlList, "Edit1")
		ControlGetText, text ,Edit1, A
	else if InStr(controlList, "TConTEXTSynEdit1")
		ControlGetText, text ,TConTEXTSynEdit1, A
	else if InStr(controlList, "RICHEDIT50W1")
		ControlGetText, text ,RICHEDIT50W1, A
	else if InStr(controlList, "Scintilla1")
		ControlGetText, text ,Scintilla1, A
	return (text ? text : "")
}




AlphaSortList(list){
StringSplit, line, list, `n,`r
Loop, parse, list, `n, `r
	data1 .= (data1?"`n":"") A_LoopField "|" A_Index
data1 := RegExReplace(data1, "[ `,“”\(\)\/`:]")
sort, data1
loop, parse, data1, `n,`r
	l := RegExReplace(A_LoopField, ".*\|")	, out .= (out?"`n":"") line%l%
Return out
}


; returns a variable containing names of all keys pressed down currently.
; Pass "L" (default) to check only the logical state of the keys, and "P" to check the physical state
GetAllKeysPressed(mode = "L") {
SetBatchLines, -1
		keys = ``|1|2|3|4|5|6|7|8|9|0|-|=|[|]\|;|'|,|.|/|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|Esc|Tab|CapsLock|LShift|RShift|LCtrl|RCtrl|LWin|RWin|LAlt|RAlt|Space|AppsKey|Up|Down|Left|Right|Enter|BackSpace|Delete|Home|End|PGUP|PGDN|PrintScreen|ScrollLock|Pause|Insert|NumLock|F1|F2|F3|F4|F5|F6|F7|F8|F9|F10|F11|F12|F13|F14|F15|F16|F17|F18|F19|F20
	; '|' isn't a key itself (with '\' being the "actual" key), so okay to use is as a delimiter
	Loop Parse, keys, |
	{		
		key = %A_LoopField%				
		isDown :=  GetKeyState(key, mode)
		if isDown
			pressed .= key	
	}   
SetBatchLines, 10ms
	return pressed
}
Last edited by brutus_skywalker on 13 Jun 2018, 09:29, edited 20 times in total.
Outsourcing Clicks & Presses Since 2004.
LarryC
Posts: 28
Joined: 30 Sep 2013, 12:14

Re: AutocompleteEditorText - using text in editor

13 May 2018, 13:13

I read this twice but still cannot find how to insert an offered word other than a statement saying to press the corresponding number key. Didn't work, but does work if double pressed the corresponding number.
brutus_skywalker
Posts: 175
Joined: 24 Dec 2016, 13:16
Location: Antarctica

Re: AutocompleteEditorText - using text in editor

13 May 2018, 23:40

LarryC wrote:I read this twice but still cannot find how to insert an offered word other than a statement saying to press the corresponding number key. Didn't work, but does work if double pressed the corresponding number.
YUP. That was bad instruction on my part. Fixed it. Double Press Number Keys.
Outsourcing Clicks & Presses Since 2004.
A_AhkUser
Posts: 1147
Joined: 06 Mar 2017, 16:18
Location: France
Contact:

Re: AutocompleteEditorText - using text in editor

21 May 2018, 13:56

brutus_skywalker wrote:^If AnyOne can make a listview alternative to this method of selection,by all means...HELP :headwall: ...
A owned GUI endowed with the WS_EX_CLICKTHROUGH (or WS_EX_NOACTIVATE) extended style, and shown using the NA option is functionally equivalent in many respects to a tooltip:

Code: Select all

#NoEnv
#SingleInstance force
#Warn

CoordMode, Caret, Screen

WinWait, ahk_class Notepad
ownerID := WinExist() ; WinExist with no params >>> retrieve the ID of the last found window, here set by the previous call of winwait as soon as it times out
GUI, New, +hwndGUIHwnd +ToolWindow -Caption +E0x20 +LastFound +Owner%ownerID% ; E0x20 >>> clickthrough GUI ; +last found > useful for the winset command above wich operates reliably on the last found window
GUI, Color, FFFFFF, 42c3dd
GUI, Font, s11, Segoe UI
WinSet, Transparent, 255 ; in order to actually apply the +E0x20 extended style
GUI, Margin, 0, 0
GUI, Add, ListBox, x0 y0 w160 -HScroll +VScroll -Multi Choose1 +Sort hwndlbHwnd +AltSubmit, ceci|est|un|test|blabla|etc.
lbahkid := "ahk_id " lbHwnd
return

#IfWinActive ahk_class Notepad
	!i::
		if not ((A_CaretX+0 <> "") && (A_CaretY+0 <> "")) ; if there's no caret...
			return
		SendMessage, 0x18b, 0, 0,, % lbahkid ; LB_GETCOUNT sets ErrorLevel to be the number of items in a list box
		count := ErrorLevel
		SendMessage, 0x1A1, 0, 0,, % lbahkid ; LB_GETITEMHEIGHT sets ErrorLevel to be the height of items in a list box
		h := (ErrorLevel + 1) * count
		GuiControl, Move, % lbHwnd, % "h" h ; set listbox's height accordingly
		x1 := A_CaretX + 20, y1 := A_CaretY + 35, x2 := x1 + 160, y2 := y1 + h
		x := (x2 > A_ScreenWidth) ? A_ScreenWidth - 160 : x1, y := (y2 > A_ScreenHeight) ? A_ScreenHeight - h : y1
		GUI %GUIHwnd%:Show, % "NA " . Format("x{1} y{2} h{3}", x, y, h) ; NA no activates the GUI so that show does not steal current window's focus
	return

	^1::
	^2::
	^3::
	^4::
	^5::
	^6::
		Control, Choose, % SubStr(A_ThisHotkey, 0),, % lbahkid
	return
#IfWinActive
my scripts
brutus_skywalker
Posts: 175
Joined: 24 Dec 2016, 13:16
Location: Antarctica

Re: AutocompleteEditorText - using text in editor

22 May 2018, 05:03

@A_AhkUser
Thanks much,a hell of a nudge in the right direction,appreciate it. Just the example I needed to wrap my head round it. ;)
Outsourcing Clicks & Presses Since 2004.
brutus_skywalker
Posts: 175
Joined: 24 Dec 2016, 13:16
Location: Antarctica

Re: AutocompleteEditorText - using text in editor

22 May 2018, 18:06

@A_AhkUser
So i more or less get list boxes now,i never got them because i never really tried,it seems...

However i don't care to reinvent the wheel, so i used your library and the attach example as a starting point for a new version below, and it does not seem to want to work with anything but an edit1 control and downright goes crazy with scintilla controls, care to enlighten me as to what i'm doing wrong. Works brilliantly with edit1 controls though.

Code: Select all

;
;
; AutoHotkey:	 v1.1.26.01 Unicode 32-bit (Installed)
; Language:		 English
; Author:        A_AhkUser wrote the lib and the example script from which this is derived,brutus_skywalker just wrote the active wordlist parsing
; OS:  			 WIN_7 64-bit Service Pack 1 v6.1.7601 (WIN32_NT)  , 6050.68MB RAM
; Date:          Wednesday, May 23, 2018 0:37
;

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
;#Warn  ; Recommended for catching common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
#SingleInstance, Force
;#NoTrayIcon


#Include eAutocomplete.ahk


While !GetTextFromControl()		;wait until supported edit control is active.
	Sleep 10


Gosub, winChanged		;initialise eAutocomplete & wordlist from active window
SetTimer, winChanged, 500		;check for window change,to update wordlist on window change.
SetTimer, updateWordList, 2000	;attempt to update wordlist every 2sec since last wordlist update,holding until user releases all keys before proceeding with update.

Loop, 3
	Menu, startAt, Add, % a_index, startAt
Menu, Tray, Add, &Start at..., :startAt
Menu, startAt, Check, % A.startAt
Menu, Tray, Add, &AutoAppend, autoAppend
Menu, Tray, Add, &Regular expressions (*), matchModeRegEx
Menu, Tray, Check, &Regular expressions (*)
Menu, Tray, Add, Append &new occurrences, appendHapax
Menu, Tray, Add,
Menu, Tray, Add, &Disable, disabled
Menu, Tray, Add, &Exit, exit
Menu, Tray, NoStandard
OnExit, handleExit
return


startAt:
	Menu, startAt, UnCheck, % A.startAt
	Menu, startAt, Check, % (A.startAt:=A_ThisMenuItemPos)
return

autoAppend:
matchModeRegEx:
appendHapax:
	A[ A_ThisLabel ] := !A[ A_ThisLabel ]
	Menu, Tray, ToggleCheck, % A_ThisMenuItem
return

disabled:
	Menu, Tray, Rename, % A_ThisMenuItem, % (A.disabled:=!A.disabled) ? "&Enable" : "&Disable"
return


exit:
handleExit:
A.dispose()
ExitApp


winChanged:
If (WinChanged() AND GetTextFromControl()){		;GetTextFromControl() only returns value if a supported edit control is detected
;reattach to supported edit control on active window
	WinGet, controlList, ControlList, A
	WinGet, ProcName , ProcessName, A

	;currently scintilla edit controls appear incompatible.
	if InStr(controlList, "Edit3"){
		ControlGet, eHwnd, Hwnd,, Edit3, % "ahk_id " . WinExist()
		A.dispose()
		A := eAutocomplete.attach(WinExist(), eHwnd)
	}else if InStr(controlList, "Edit1"){
		ControlGet, eHwnd, Hwnd,, Edit1, % "ahk_id " . WinExist()
		A.dispose()
		A := eAutocomplete.attach(WinExist(), eHwnd)
	}else if InStr(controlList, "TConTEXTSynEdit1"){
		ControlGet, eHwnd, Hwnd,, TConTEXTSynEdit1, % "ahk_id " . WinExist()
		A.dispose()
		A := eAutocomplete.attach(WinExist(), eHwnd)
	}else if InStr(controlList, "RICHEDIT50W1"){
		ControlGet, eHwnd, Hwnd,, RICHEDIT50W1, % "ahk_id " . WinExist()
		A.dispose()
		A := eAutocomplete.attach(WinExist(), eHwnd)
	}
	
Gosub, updateWordList
}
Return


updateWordList:
	SetBatchLines, -1	;go as fast as possible to reduce update delay
	
	thisEditorText := GetTextFromControl()

	;don't rebuild word list if text content of window hasn't changed
	IfEqual, thisEditorText, %lastEditorText%
		{
		SetBatchLines, 10ms	;default speed
		Return
		}

	SetTimer, updateWordList, off
	While GetAllKeysPressed(){		;wait until user is done typing before updating the word list further
		Sleep, 1000
		}
	SetTimer, updateWordList, on

		
	;Get Wordlist
	this_WordList := AlphaSortList(GetActiveWinWordList())		;get & sort worldlist from active window text
	If (!this_WordList){	;if current window has no wordlist,retain old one.
		SetBatchLines, 10ms	;default speed
		Return
		}


	;rebuild wordlist
	A.addSource("dynamicWordList", this_WordList, "`n")
	A.setSource("dynamicWordList") ; defines the word list to use
	; ToolTip, %WordList%
	SoundBeep					;DEBUG: SIGNIFY UPDATE
	
	lastEditorText := thisEditorText
	SetBatchLines, 10ms	;default speed
	Return



GetActiveWinWordList(){
	Static lastText
	Static lastWordList
	text := GetTextFromControl()
	If !text	;if no text can be retrieved from a control get all text in window
		WinGetText, text, A
	;rebuild WordList only if text in editor has changed
	IfEqual, text, %lastText%
		Return lastWordList
	lastText := text
	StringReplace, text, text, `,, `n, ALL
	StringReplace, text, text, `r`n, `n, ALL
	StringReplace, text, text, %A_Space%, `n, ALL
	StringReplace, text, text, %A_Tab%, `n, ALL
	;remove white spaces
	wordListText := RegExReplace(text , "(^|\R)\K\s+")
	;rebuild wordlist using only strings longer than 3 characters
	Wordlist:=""
	Loop, Parse, wordListText, `n
		If StrLen(A_LoopField) >= 3		;strings of over 3 characters
			IfNotInString, Wordlist, %A_Space%%A_LoopField%%A_Space%	;avoid repeated strings
				Wordlist.= " " A_LoopField " " "`n"	;add spaces on either side so that words in other word can easily be added to list
	StringReplace, Wordlist, Wordlist, %A_Space%, , ALL ;spaces are no longer needed as list is done building
	lastWordList := WordList
	Return WordList
}




GetTextFromControl(){
	WinGet, controlList, ControlList, A
	WinGet, ProcName , ProcessName, A

	if InStr(controlList, "Edit3")
		ControlGetText, text ,Edit3, A
	else if InStr(controlList, "Edit1")
		ControlGetText, text ,Edit1, A
	else if InStr(controlList, "TConTEXTSynEdit1")
		ControlGetText, text ,TConTEXTSynEdit1, A
	else if InStr(controlList, "RICHEDIT50W1")
		ControlGetText, text ,RICHEDIT50W1, A

	return (text ? text : "")
}




AlphaSortList(list){
StringSplit, line, list, `n,`r
Loop, parse, list, `n, `r
	data1 .= (data1?"`n":"") A_LoopField "|" A_Index
data1 := RegExReplace(data1, "[ `,“”\(\)\/`:]")
sort, data1
loop, parse, data1, `n,`r
	l := RegExReplace(A_LoopField, ".*\|")	, out .= (out?"`n":"") line%l%
Return out
}


; returns an variable containing names of all keys pressed down currently.
; Pass "L" (default) to check only the logical state of the keys, and "P" to check the physical state
GetAllKeysPressed(mode = "L") {
SetBatchLines, -1
		keys = ``|1|2|3|4|5|6|7|8|9|0|-|=|[|]\|;|'|,|.|/|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|Esc|Tab|CapsLock|LShift|RShift|LCtrl|RCtrl|LWin|RWin|LAlt|RAlt|Space|AppsKey|Up|Down|Left|Right|Enter|BackSpace|Delete|Home|End|PGUP|PGDN|PrintScreen|ScrollLock|Pause|Insert|NumLock|F1|F2|F3|F4|F5|F6|F7|F8|F9|F10|F11|F12|F13|F14|F15|F16|F17|F18|F19|F20
	; '|' isn't a key itself (with '\' being the "actual" key), so okay to use is as a delimiter
	Loop Parse, keys, |
	{		
		key = %A_LoopField%				
		isDown :=  GetKeyState(key, mode)
		if isDown
			pressed .= key	
	}   
SetBatchLines, 10ms
	return pressed
}


WinChanged(){
	static lastHwnd
	If ( WinExist("A") != lastHwnd ){
		lastHwnd := WinExist("A")
		Return True
	}
}
Last edited by brutus_skywalker on 25 May 2018, 03:12, edited 1 time in total.
Outsourcing Clicks & Presses Since 2004.
A_AhkUser
Posts: 1147
Joined: 06 Mar 2017, 16:18
Location: France
Contact:

Re: AutocompleteEditorText - using text in editor

23 May 2018, 21:03

brutus_skywalker wrote:However i don't care to reinvent the wheel, so i used your library and the attach example as a starting point for a new version below, and it does not seem to want to work with anything but an edit1 control and downright goes crazy with scintilla controls, care to enlighten me as to what i'm doing wrong. Works brilliantly with edit1 controls though.
I'm glad you give a chance to the library... but is intended to work only with edit control... - hence the 'e' in eAutocomplete. The class internally relies, among other things, on messages (to set/retrieve the selection in the control, for example) which are specific to edit/richedit controls; hence it goes crazy with - the very specific - scintilla control (btw your code has the virtue to implicitly points out that the class probably needs a control class validator before even a 'control class dispatcher' like the one in your winChanged subroutine). Practically, it means the class will work on paper with both edit and combobox controls, since these latter have an embedded (child) edit control - on paper: I didn't test with combobox controls (btw, in this case it might be more relevant, depending on goals, to use cbAutocomplete by Pulover). Simply, I had not originally take into consideration other kind of control and the script reflects it by its limitations.
That's also the reason why I posted with true peace of mind the eAutocomplete class a few hours after your autocomplete script: to some extent, they share the same goals but yours has the undeniable and specific advantage to work with other kind of controls.

However, I really like how you make the best of the class by attaching dynamically compatible controls and, more specifically, the idea to dynamically set the source depending on the active window. This could actually be tremendously useful when using multiple keyboard layouts and/or various registers of language depending on the active application/web page. This is very interesting. In any event, it makes me want to have a support for other kind of controls. It might be at least possible to add - even in the form of a new library - a full support for richedit control (e.g. word) and textarea html element (in an embedded browser). I shall take it into consideration.

On a side note, note that:

Code: Select all

A := eAutocomplete.attach(WinExist(), eHwnd)
does not implictly remove the event hook function nor dispose the previous instance since each instance set circular references; alternatively, you might consider to dispose the previous instance before anew calling the attach method in order to update the host edit control.

Thanks for sharing this implementation, really, much appreciated.

P.S.: To which application(s) TConTEXTSynEdit1 belongs?
my scripts
brutus_skywalker
Posts: 175
Joined: 24 Dec 2016, 13:16
Location: Antarctica

Re: AutocompleteEditorText - using text in editor

25 May 2018, 00:34

A_AhkUser wrote: ... but is intended to work only with edit control... - hence the 'e' in eAutocomplete.
Yeah, i thought as much, and i eventually tried cbAutocomplete by pullover, but it was a bit drab and unflattering for an autocomplete interface, so i'm writing a 'Pseudo-Micro-Framework' with verbose commentary for listboxes intended for text selection and as a starting point for anyone else dabling with list boxes,which i am also going to use for a listbox suggestion variant to 'AutoCompleteEditorText'.
A_AhkUser wrote: ..btw your code has the virtue to implicitly points out that the class probably needs a control class validator before even a 'control class dispatcher' like the one in your winChanged subroutine. Practically, it means the class will work on paper with both edit and combobox controls, since these latter have an embedded (child) edit control - on paper:...
Hmm...
A_AhkUser wrote: It might be at least possible to add - even in the form of a new library - a full support for richedit control (e.g. word) and textarea html element (in an embedded browser). I shall take it into consideration.
I hope you do consider it, your library trully is awesome. Maybe even someone else will expand on it,that tends to be the trend in this forum, a very welcome trend...
A_AhkUser wrote: ...you might consider to dispose the previous instance before anew calling the attach method in order to update the host edit control.
Aha, i knew i saw a dispose call, on the exit subroutine on your example, i failed to incorporate it, for some reason!
A_AhkUser wrote: P.S.: To which application(s) TConTEXTSynEdit1 belongs?
An obscure editor called ConTEXT, http://www.contexteditor.org/index.php


Thanks A_AhkUser, your insight is most welcome.
Outsourcing Clicks & Presses Since 2004.
brutus_skywalker
Posts: 175
Joined: 24 Dec 2016, 13:16
Location: Antarctica

Re: [Script] AutocompleteEditorText - using text from editor/active window [LISTBOX Rewrite]

26 May 2018, 09:03

Updated OP, with listbox variant of my OP. A lot better dare i say....
Outsourcing Clicks & Presses Since 2004.
A_AhkUser
Posts: 1147
Joined: 06 Mar 2017, 16:18
Location: France
Contact:

Re: [Script] AutocompleteEditorText - using text from editor/active window [LISTBOX Rewrite]

27 May 2018, 23:02

brutus_skywalker wrote:Updated OP, with listbox variant of my OP. A lot better dare i say....
Nice work :bravo:
Works well here (tested on both Notepad and Notepad++ - still have to test it on... ConTEXT though :D ). Erratically, I have some parenthesis or dots in some suggestions (e.g. 'name:').
One question:
  • Is there any reason why you make it (re)create/destroy the GUI instead of simply show/hide it (CreateListBox vs UpdateListBox) ?
If I may, two remarks:
  • The use of ByRef parameters could be relevant for both the WordsStartWithString & AlphaSortList functions:
    Documentation - ByRef parameters wrote:When passing large strings to a function, ByRef enhances performance and conserves memory by avoiding the need to make a copy of the string. Similarly, using ByRef to send a long string back to the caller usually performs better than something like Return HugeString
  • AutocompleteEditorText -ListBox-.ahk wrote:Create transparent Hotkeys for all relevant keys
    I doubt that french people consider é to not being part of relevant keys :terms: :lol: If I type 'été' or 'événement' it fails to actually suggest the given words (but it works well with 'littéraire')... Maybe scancodes could be a workaround here - I saw Exaskryz used something like the following one day:

    Code: Select all

    Loop % 100 ; not sure which number to use here
    	Hotkey % "~sc" . a_index, test, On
    return
    
    test:
    ToolTip % GetKeyName(LTrim(A_ThisHotkey, "~"))
    return
    
    !i::KeyHistory

    ... so that you hotkeys are less hard-coded and will work for languages like russian (just an idea).
Cheers
my scripts
brutus_skywalker
Posts: 175
Joined: 24 Dec 2016, 13:16
Location: Antarctica

Re: [Script] AutocompleteEditorText - using text from editor/active window [LISTBOX Rewrite]

29 May 2018, 13:47

A_AhkUser wrote: Erratically, I have some parenthesis or dots in some suggestions (e.g. 'name:').
That was intentional, a great many strings have useful punctuations(Periods,Function(),file names,.), but i added a config option to remove punctuations from results. Default is with punctuations though.
A_AhkUser wrote: ....
Is there any reason why you make it (re)create/destroy the GUI instead of simply show/hide it (CreateListBox vs UpdateListBox) ?
...
The use of ByRef parameters could be relevant for both the WordsStartWithString & AlphaSortList functions:
...
I didn't factor in performance,hence forgot ByRef and destroy instead of hide was to do with having issues with the context sensitve hotkeys,which needed to be inactive when the GUI was not active,but i have opted for an alternate method of exposing a hidden gui to the CreateListBox function momentarily to update it,which seems to work just fine. I know i could just use a control variable as in #If WinExist(listBoxTitle) AND !listboxHidden, but for some reason it's not properly working.

Edit: Properly using #If now, issue was on my end.

Corrected Both,

A_AhkUser wrote:
I doubt that french people consider é to not being part of relevant keys :terms: :lol: If I type 'été' or 'événement' it fails to actually suggest the given words (but it works well with 'littéraire')... Maybe scancodes could be a workaround here....

... so that you hotkeys are less hard-coded and will work for languages like russian (just an idea).
Well i just added an additional input capture function,GetInputString, to supplement hotkeys, It' uses the ' Input command, and captures all keys that aren't hotkeys, it seems to work with characters like 'ö,ß,' when i switched to german keyboard and 'ù' when i was on french, though i couldn't test the not so french é which ended up being detected but also input alongside the number two... So test it if you could, and let me know if it works on a native keyboard... And the scan code method seems to be even worse off, example the scan code for z is SC02C, x is SC02D, so looping through numbers will only get you so many characters.

Here's a script to test if a key is detected at all, it uses the same input detection as in my update,so if works here it should work there.Cheers.

And Most definitely thanks for your feedback and input,as always. :wink:

Code: Select all

Loop
	ToolTip, % GetInputString()

GetInputString(){
Static
	;Get one key at a time
	Input, chr, L1 V,{enter}{space}.;`,:¿?¡!'"()]{}{bs}{esc}
	EndKey = %errorlevel%
	IfInString, EndKey, Endkey
		IfNotInString, EndKey, Endkey:BackSpace
			word:= ""
	IfEqual, EndKey, Endkey:BackSpace
		StringTrimRight, word, word, 1
	IfNotEqual, EndKey, Endkey:BackSpace,
		word = %word%%chr%
	Return, word
}

Outsourcing Clicks & Presses Since 2004.
brutus_skywalker
Posts: 175
Joined: 24 Dec 2016, 13:16
Location: Antarctica

Re: [Script] AutocompleteEditorText - using text from editor/active-window/last-window

31 May 2018, 08:22

Updated to a Fully functional and configurable script, will fix all reported bugs and any i find while i use it, but no additional features planned from here on out,unless you suggest a really good one :think: ...,it's fairly ironed out,been doing that for the past 3days. *Retained all commented-out debug code & verbose commentary for others to best understand the script. :beer:
Outsourcing Clicks & Presses Since 2004.
A_AhkUser
Posts: 1147
Joined: 06 Mar 2017, 16:18
Location: France
Contact:

Re: [Script] AutocompleteEditorText - using text from editor/active window [LISTBOX Rewrite]

03 Jun 2018, 19:26

brutus_skywalker wrote:So test it if you could, and let me know if it works on a native keyboard...
It does work ;) :bravo:

getDifferentialInput:
Spoiler
my scripts
brutus_skywalker
Posts: 175
Joined: 24 Dec 2016, 13:16
Location: Antarctica

Re: [Script] AutocompleteEditorText - using text from editor/active window [LISTBOX Rewrite]

04 Jun 2018, 10:56

A_AhkUser wrote:
brutus_skywalker wrote:So test it if you could, and let me know if it works on a native keyboard...
It does work ;) :bravo:

getDifferentialInput:
Spoiler

That does help, thanks. I dunno how,but i was deleting a very old post and somehow my last post got deleted instead. I had yet to repost, did it now though.
Helgef wrote: Great stuff, thanks for sharing .

Cheers.
Thanks, check out my last update, fixed a few major issues i missed before.
Outsourcing Clicks & Presses Since 2004.

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 137 guests