Well, sorry, System Monitor and Ian... looks like I beat you to it.
Update:: replaced ControlGetFocus with the workaround supplied in the help docs. This may perform better than the previous version, however it no longer checks the type of control which is currently focused (although the script probably won't detect any changes in the text of a non-edit, non-cbbox control.
I suppose I forgot to clearly state the scope of this script:
Purpose: In a plain text editor, this script is meant to offer a one-touch hotkey that will fill in the estimated remaining letters of a partially typed word under the following conditions: either the word is contained in a list of words that the user has previously completed using this hotkey OR the word is contained in a list of words that the user has recently typed.
This script will never actually send keys without specific action on the user's part (via the specified hotkey).
Intended Use: for programmers who habitually misspell their variable and function names and/or get tired of repeatedly typing in long but very descriptive variable names.
Here's version 2² of my Anti-RSI Script:
Code:
; RSI helper ... [VxE] style! ___ Version 2.0
; Keeps track of the words you type and offers to fill in the rest of a
; recognized word.
; Order of priority is given first to any matches in the VIP list.
; Order of Priority is then based on the last time a matching word was used.
;////////////////////////////////////////////////// Config Section
History_Length = 100 ; how long is our normal history?
; Our VIP history is eternal and infinite !! (BWWAAAHHAHAhahahehehuhuhmeh)
Selector_Hotkey = RShift ; This hotkey becomes available when the script makes a guess
Needle_Min_Length = 3 ; how many letters before the script makes a guess ?
; NOTE: words with a length less than this value+2 will not appear in the history
Keep_History_In_File = MyRSIHelperHistory.txt
Restrict_To_Window = Notepad ; set to "A" for Any Active window
; KNOWN COMPATIBILITY WITH PLAIN-TEXT EDITORS ONLY !!!
Terminal_Chars := "`n`r`t ,." ; These mark effective word breaks
;////////////////////////////////////////////////// End Config
SetTimer, PollCurrentControl, 200
SetWorkingDir, %A_ScriptDir%
SetTitleMatchMode, 2
#SingleInstance Force
If Keep_History_In_File
{
OnExit, SaveHistory
IfExist, %Keep_History_In_File%
{
FileRead, WordDB, %Keep_History_In_File%
cdb = MyVIPWords
Loop, Parse, WordDB, `n, `r
If InStr(A_LoopField, A_Space) || !A_LoopField
{
If A_LoopField = My Word History
cdb = MyWordHistory
If InStr( A_LoopField, "History Length =" ) = 1
History_Length := SubStr( A_LoopField, 17 ) +0
If InStr( A_LoopField, "Selector Hotkey =" ) = 1
Selector_Hotkey := SubStr( A_LoopField, 18 )
If InStr( A_LoopField, "Needle Minimum Length =" ) = 1
Needle_Min_Length := SubStr( A_LoopField, 25 ) +0
If InStr( A_LoopField, "Restrict to Window =" ) = 1
Restrict_To_Window := SubStr( A_LoopField, 21 )
}
Else
%cdb% .= "`n" A_LoopField
}
}
AutoTrim, on
History_Length = %History_Length%
Selector_Hotkey = %Selector_Hotkey%
Needle_Min_Length = %Needle_Min_Length%
Restrict_To_Window = %Restrict_To_Window%
If Restrict_To_Window = A
Restrict_To_Window =
; This is the meat of the script. Had to set a sleep on the LButton cuz the script
; was disabling the double click to select a word :(
; Anyways, this script snatches the current edit control's text, scans it for changes
; then builds or clears a needle based on the difference. An ending char following
; a long enough needle causes that needle to become a word in the history.
PollCurrentControl:
IFWinNotActive %Restrict_To_Window%
{
tooltip
return
}
;;;;;;;;;;; copied from help docs on "controlgetfocus" command
GuiThreadInfoSize = 48
VarSetCapacity(GuiThreadInfo, GuiThreadInfoSize)
InsertInteger(GuiThreadInfoSize, GuiThreadInfo, 0)
if not DllCall("GetGUIThreadInfo", uint, 0, str, GuiThreadInfo)
return
CHWND := ExtractInteger(GuiThreadInfo, 12) ; Retrieve the hwndFocus field from the struct.
;;;;;;;;;;;;;;;;;;;; end of plagarism section :-D ;;;;;;;;;;;;;
If OldFocus != %chwnd%
oldtex := needle := ""
ControlGetText, testex, , Ahk_Id %chwnd%
testex := StripFormatting(testex) ; handle formatted text
If ( testex != oldtex ) ; On current control text change
{
Loop, Parse, testex
{
If ( SubStr(testex, A_Index, 1) != SubStr(oldtex, A_Index, 1) )
{
atex := SubStr( testex, A_Index )
btex := SubStr( oldtex, A_Index )
ctex := SubStr( oldtex, 1, A_Index-1)
If (bPos := InStr(btex, atex)) ; characters have been deleted (bksp / del / cut)
{
needle := ""
break
}
If !(bPos := InStr(atex, btex)) ; characters have been changed (select paste / insert mode )
Loop, % StrLen( btex )
If InStr(atext, oldtex := SubStr(btex, A_Index+1))
{
btex := oldtex
break
}
If (bPos := InStr(atex, btex)) ; characters have been added
{
AppChar := SubStr( atex, 1, bPos-1 ) ; retrieve the added chars
StringReplace, AppChar, AppChar, `r
bPos := InStr( cTex, A_Space, 0,0 )
Loop, Parse, Terminal_Chars ; rebuild the needle
If (bPos < (nPos := InStr( cTex, A_LoopField, 0,0)) || A_Index = 1 )
bPos := nPos
needle := SubStr(cTex, bPos+1) . AppChar
bPos := InStr( needle, A_Space )
Loop, Parse, Terminal_Chars
If (nPos := InStr( needle, A_LoopField )) && (( npos < bpos ) || !bPos)
bPos := nPos
If bPos
{
word := SubStr( needle, 1, bPos-1 )
If (StrLen(Word) > 1 + Needle_Min_Length )
MyWordHistory := AddMWHistory( MyWordHistory, Word )
needle := ""
}
}
break
}
}
}
If (StrLen(Needle) >= Needle_Min_Length )
&& ( bPos := InStr( WordDB := MyVIPWords "`n" MyWordHistory "`n", "`n" needle))
{
; we have a match, so tooltip out the part that we want to send
Word := SubStr( WordDB, bPos+1, InStr(WordDB, "`n", 0, bPos+1) - bPos - 1)
If Word = %needle%
{
tooltip
Hotkey, %Selector_Hotkey%, hklabel, off
StatusFlag =
}
else
{
tooltip, % SubStr(Word, StrLen(needle)+1), %A_CaretX%, % A_CaretY - 25
Hotkey, %Selector_Hotkey%, hklabel, on
StatusFlag = Matched and pending user action
}
}
else If StatusFlag
{
tooltip
Hotkey, %Selector_Hotkey%, hklabel, off
StatusFlag =
}
oldtex := testex
OldFocus = %chwnd%
return
; Not much to explain here... explain wouldn't even be in a function
; if it were'nt used exactly the same way in 2 different places
AddMWHistory( db, item )
{
Stringreplace, db, db, `n%item%`n, `n`n, a
Stringreplace, db, db, `n`n, `n, a
Return % "`n" item "`n" db
}
StripFormatting(str)
{
; future implementation to handle edit controls in word processing progs and such
; currently, things like rich-text tags and M$ Word formatting cannot be handled.
return % "`n`n`n`n`n`n" str "`n«º»`n«ª»"
}
; The chosen hotkey. Heep in mind that this hotkey is turned off at all times except when this
; script has suggested a word to complete. In that case this hotkey pastes the word.
hklabel:
MyVIPWords := AddMWHistory( MyVIPWords, Word )
Send % "{blind}" SubStr(Word, StrLen(needle)+1) . (needle := "")
return
; pretty obvious, no?
saveHistory:
If Keep_History_In_File
{
FileDelete, %Keep_History_In_File%
sleep 50
FileAppend, % "History Length = `t" History_Length
. "`nSelector Hotkey = `t" Selector_Hotkey
. "`nNeedle Min Length =`t" Needle_Min_Length
. "`nRestrict To Window =`t" ((Restrict_To_Window) ? (Restrict_To_Window) : ("A"))
. "`n`nMy VIP Words`n" MyVIPWords
. "`n`nMy Word History`n" MyWordHistory
. "" , %Keep_History_In_File%
}
Exitapp
~` & esc::
Exitapp ; The End... and all the little trolls lived happily ever after.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; More stuff copied from the manual
ExtractInteger(ByRef pSource, pOffset = 0, pIsSigned = false, pSize = 4)
; pSource is a string (buffer) whose memory area contains a raw/binary integer at pOffset.
; The caller should pass true for pSigned to interpret the result as signed vs. unsigned.
; pSize is the size of PSource's integer in bytes (e.g. 4 bytes for a DWORD or Int).
; pSource must be ByRef to avoid corruption during the formal-to-actual copying process
; (since pSource might contain valid data beyond its first binary zero).
{
Loop %pSize% ; Build the integer by adding up its bytes.
result += *(&pSource + pOffset + A_Index-1) << 8*(A_Index-1)
if (!pIsSigned OR pSize > 4 OR result < 0x80000000)
return result ; Signed vs. unsigned doesn't matter in these cases.
; Otherwise, convert the value (now known to be 32-bit) to its signed counterpart:
return -(0xFFFFFFFF - result + 1)
}
InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; The caller must ensure that pDest has sufficient capacity. To preserve any existing contents in pDest,
; only pSize number of bytes starting at pOffset are altered in it.
{
Loop %pSize% ; Copy each byte in the integer into the structure as raw binary data.
DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; That's it for now
What's New:
1. Changed how the script handles saving the word list and fixed that bugginess. Saved data has been tested somewhat.
2. Changed the method of needle construction. Now the needle is independent of prior input ( meaning that you can move the caret to a partially completed word, type a char, and the script will use whatever part of that word that is to the left of the caret as the needle).
3. The period of the main loop is a
WHOPPING HUGE 200 ms, just to illustrate how this script can handle input at any speed. You can even copy/paste pieces of words and they will be correctly handled by the next needle.
4. I forgot to mention before, but this is very important
This script will not work in a formatted text editor!!!!. It works fine in notepad
5. Not exactly new, but, this script will become immediately
dormant if a non-edit control is in focus or if a non-matching
window becomes active.