Multi-line version of Hotstring Helper

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
kunkel321
Posts: 1057
Joined: 30 Nov 2015, 21:19

Multi-line version of Hotstring Helper

Post by kunkel321 » 04 Mar 2023, 14:40

I guess this is useful enough to post here...

This is all v1 stuff. The actual Hotstring Helper is found at the bottom of this page:
https://www.autohotkey.com/docs/v1/lib/Hotstring.htm

THe original HH has some lines of code to standardize the types of new line characters used. I haven't added that yet, but will. The first version of my script (and an explanation) is here:
viewtopic.php?f=76&t=114580&p=510514#p510514

Screenshot
Spoiler
Code
Newer versions below in replies.
Spoiler
Select some text before activating -- or not.

LATER EDIT:
Well... :oops:
Upon studying the above mentioned replacements in the original HH code.
Referring to this:
Spoiler

I see that it ALREADY supports multi-line replacements stings.

Oh well... I guess folks have more options now.

Code: Select all

;Original HotstringHelper saves as...
::wdays1::Monday`rTuesday`rWednseday`rThursday`rFriday`rSaturday`rSunday

; Kunkel321's version here saves as...
::wdays2::
(
Monday
Tuesday
Wednseday
Thursday
Friday
Saturday
Sunday
)
Both create the same thing when expanded.
Last edited by kunkel321 on 07 Mar 2023, 18:13, edited 1 time in total.
ste(phen|ve) kunkel

User avatar
kunkel321
Posts: 1057
Joined: 30 Nov 2015, 21:19

Re: Multi-line version of Hotstring Helper

Post by kunkel321 » 06 Mar 2023, 18:29

A couple minor improvements...
Spoiler
1. It now pops up near the top/left of the active window (which is where you're likely to need it).
2. There is a "validate" button, but validation will also happen if you click "Append." (see screenshot) The MsgBox only appears if there is a problem. Mostly this will just help me remember to put a replacement string. Also, if anything is entered in the Options box that isn't listed as one of the Options in the v1 docs, then the MsgBox will indicate what it was, as well as showing a list of tips.
Spoiler
Note that I didn't put the before-mentioned StringReplace commands that are in the original HotString Helper script. It occurred to me that this would be moot since I'm using hotstrings with continuation sections inside parentheses...

An interesting observation related to this: You can add a string like this
line one`nline two`nline three and it will save the string, using only one line of code as seen in the next screenshot.
Spoiler
This is interesting because the script looks for the presence of "`n" in the replacement string, and--if found--adds the open and close parantheses above and below. Apparently it doesn't see the `n characters in
line one`nline two`nline three though.
Whatever the reason, both of these (qq1 and qq2) seem to expand as you would want them to.
ste(phen|ve) kunkel

User avatar
kunkel321
Posts: 1057
Joined: 30 Nov 2015, 21:19

Re: Multi-line version of Hotstring Helper

Post by kunkel321 » 07 Mar 2023, 18:25

I have a long-used custom version of the ubiquitous AutoCorrect.ahk. I replaced the classic Hotstring Helper component with my own and have been using it. I mostly have it doing what I want, so I probably won't do anymore updates. "Probably"

This version allows the use of the Enter key. If you press Enter while in the Options box or the Hotstring box, then it's the same as pressing the Append button. But if you are editing the Replacement Text box and press Enter, it just types a newline. Pressing Esc closes the GUI form.

I removed the Exit button, because my AutoCorrect script is also my MasterScript, that is always running in the background--I never exit/kill it. I did add an Open button though. Pressing Open will launch the script for editing, then navigate to the bottom, where the newer-created hotstrings will be.

I made the Replacement Text box a bit smaller.

Code: Select all

#SingleInstance, Force
; A version of Hotstring Helper that will support multi-line replacements.
; https://www.autohotkey.com/boards/viewtopic.php?f=6&t=114688
; NEEDS AHK v1.1

!#h:: ; Activation key combo (not string) is Alt+Win+h.

WinGetPos, X, Y, W, H, A   ; "A" to get the active window's pos.
X := X + (W * 0.05) ; Use these with GUI Show, below.
Y := Y + (H * 0.2)

AutoTrim Off  ; Retain any leading and trailing whitespace on the clipboard.
ClipboardOld = %ClipboardAll%
Clipboard =  ; Must start off blank for detection to work.
Send ^c
ClipWait 0.3 ;

Gui, hh:Destroy
Gui, hh:-MinimizeBox +alwaysOnTop ToolWindow
Gui, hh:Font, s11

Gui, hh:Add, Text,, Add Hotstring (don't include colons)
Gui, hh:Add, Text, ys+25 xs w30 Section, Options
Gui, hh:Add, Text, x+20 w30, Hotstring
Gui, hh:Add, Edit, ys+20 xm+10 w30 vOpts Section,
Gui, hh:Add, Edit, x+28 w60 vHotStr,
Gui, hh:Add, Text, xm Section, Enter Replacement String
Gui, hh:Add, Edit, +Wrap ys+20 xs w285 h100 vRepStr, %Clipboard%
Gui, hh:Add, Button, Section, Append
Gui, hh:Add, Button, x+5, Validate
Gui, hh:Add, Button, x+5, Open
Gui, hh:Add, Button, x+5, Cancel
;~ Gui, hh:Add, Button, x+5, Exit

Gui, hh:Show, x%X% y%Y%, HotStrHelper-ML
Return

#IfWinActive, HotStrHelper-ML
Enter:: ; When Enter is pressed, but only in this GUI.
  GuiControlGet, hhControl, hh:Focus
  if (hhControl = "Edit2") || (hhControl = "Edit1") ; Edit1 is Opts, Edit2 is the Hotstring box
  {
  Gosub, hhButtonAppend
 ; MsgBox control is |%hhControl%|
  return
  }
  else
  {
    send {Enter} ; Just normal typing; Enter = Enter.
    Return
  }
Esc::
  Gui, hh:Destroy
return
#IfWinActive

hhButtonValidate:
hhButtonAppend:
;MsgBox either button pressed.
Gui, hh:Submit, NoHide
; Make sure hotstring options are valid.
NeedleRegEx := "(\*|B0|\?|C|K[0-9]{1,3}|O|R|SI|SP|SE|T|X)"
WithNeedlesRemoved := RegExReplace(Opts, NeedleRegEx, "")
If(WithNeedlesRemoved = "")
  validOpts = Okay.
else
{
	OptTips =
	(
Don't include the colons.
..from AHK v1 docs...
 * ending char not needed
 ? trigger inside other words
 B0 no backspacing
 C case-sensitive
 K(n) set key delay
 O omit end char
 R send raw
 SI send input mode
 SP send play mode
 SE send event mode
 T super raw
 X execute command
	)
  validOpts = Invalid Hotsring Options found.`n---> %WithNeedlesRemoved%`n`n`tTips:`n%OptTips%
}
; Make sure hotstring box is not empty.
If (HotStr = "")
	validHot = Hotstring box should not be empty.
else
	validHot = Okay.
; Make sure replacement string box is not empty.
If (RepStr = "")
	validRep = Replacement string box should not be empty.
else
	validRep = Okay.
CombinedValidMsg = OPTIONS BOX `n-%validOpts%`n`nHOTSTRING BOX `n-%validHot%`n`nREPLACEMENT BOX `n-%validRep%

; Which button was pressed?
if (A_ThisLabel = "hhButtonValidate")
{
 MsgBox , 4096, Validate, %CombinedValidMsg%
 return
}

if (A_ThisLabel = "hhButtonAppend")
{
; MsgBox append was pressed
 If (validOpts = "Okay.") && (validHot = "Okay.") && (validRep = "Okay.") ; if all valid
 {
  Gui, hh:Submit, Hide
  Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
  ; Check for mult-line replacment and wrap in parenths if needed.
  If inStr(RepStr,"`n")
      WholeStr :=  ":" . Opts . ":" . HotStr . "::`n(`n" . RepStr . "`n)"
  Else
      WholeStr :=  ":" . Opts . ":" . HotStr . "::" . RepStr

  ;~ MsgBox , 4096, PREVIEW, %WholeStr%
  FileAppend, `n%WholeStr%, %A_ScriptFullPath%
  Reload
  Return
 }
 else
 {
   MsgBox , 4096, Validate, %CombinedValidMsg%
   return
 }
}

; The other buttons.
hhButtonOpen: ; Open this file and go to the bottom so you can see your Hotstrings.
Gui, hh:Destroy
Edit
;Run,  %A_ScriptFullPath% ; This assumes your Windows default is to edit ahk files!!!
WinWaitActive, %A_ScriptName%
sleep, 250
Send, {Ctrl Down}{End}{Ctrl Up}{Home}
return

hhButtonCancel:
Gui, hh:Destroy
Exit
return

;~ hhButtonExit:
;~ ExitApp
;~ return

;==========================
; HotStrings appended (added)
; by the script will be down there.
; Newest ones at the bottom.
;=========================
ste(phen|ve) kunkel

User avatar
Kellyzkorner_NJ
Posts: 84
Joined: 20 Oct 2017, 18:33

Re: Multi-line version of Hotstring Helper

Post by Kellyzkorner_NJ » 10 Mar 2023, 15:51

@kunkel321

Just wanted to thank you for this. It's fantastic!

Kelly

User avatar
kunkel321
Posts: 1057
Joined: 30 Nov 2015, 21:19

Re: Multi-line version of Hotstring Helper

Post by kunkel321 » 10 Mar 2023, 17:18

You are most welcome Kelly! And thank you.

I hadn't planned on adding more features to it... But then I did anyway. :geek:

Code: Select all

#SingleInstance, Force
; A version of Hotstring Helper that will support block multi-line replacements.
; https://www.autohotkey.com/boards/viewtopic.php?f=6&t=114688
; Needs AHK v1.1
; Please get a copy of AutoHotkey.exe and rename it to match the name of this
; script file, so that the .exe and the .ahk have the same name, in the same folder.
; DO NOT COMPILE, or the Append command won't work.

!#h::   ; Activation key combo (not string) is Alt+Win+h.

AutoTrim Off  ; Retain any leading and trailing whitespace on the clipboard.
ClipboardOld = %ClipboardAll% ; Save and put back later.
Clipboard =  ; Must start off blank for detection to work.
Send ^c ; Copy selected text.
ClipWait 0.3 ; Wait for clipboard to contain text.

; If white space present in selected text, probably not an Autocorrect entry.
If (InStr(Clipboard, " ") || InStr(Clipboard, "`n"))
{
 ;=======Change=options=for=multi=word=entry=options=and=trigger=strings=as=desired==============
 myDefaultOpts := ""    ; PreEnter these hotstring options; "*" = end char not needed, etc.
 myPrefix := ";"        ; Optional character that you want suggested at the beginning of each hotstring.
 addFirstLetters := 5   ; Add first letter of this many words. (5 recommended; 0 = don't use feature.)
  tooSmallLen := 2      ; Only first letters from words longer than this. (Moot if addFirstLetters = 0)
 mySuffix := ""         ; An empty string "" means don't use feature.
;===========================================================one=more=below=======================
  If (addFirstLetters > 0)
  {
    LBLhotstring := "Edit trigger string as needed"
    initials := "" ; Initials will be the first letter of each word as a hotstring suggestion.
    HotStrSug := StrReplace(Clipboard, "`n"," ") ; Unwrap, but only for hotstr suggestion.
    Loop, Parse, HotStrSug, %A_Space%
    {
      lenW := Strlen(A_LoopField)
      If (lenW > tooSmallLen) ; Check length of each word, ignore if 2 letters.
         initials :=initials . SubStr(A_LoopField, "1", "1")
      lenHS := StrLen(initials)
      If (lenHS = addFirstLetters) ; stop looping if hotstring is 5 chars long.
         break
    }
    StringLower, initials, initials
    defaultHotStr := myPrefix . initials . mySuffix ; Append preferred prefix or suffix, as defined above, to initials.
  }
  else
  {
   LBLhotstring := "Add a trigger string"
   defaultHotStr := myPrefix . mySuffix ; Use prefix and/or suffix as needed, but no initials.
  }
}
else
{
  LBLhotstring := "Add misspelled word"
  defaultHotStr = %Clipboard% ; No spaces found so assume it's a mispelling autocorrect entry: No options, no pre/suffix.

;===============Change=options=autocorrect=words=as=desired======================================
  myDefaultOpts := ""    ; PreEnter these autocorrect options; "T" = raw text mode, etc.
;================================================================================================
}

WinGetPos, X, Y, W, H, A   ; "A" to get the active window's position on screen.
X := X + (W * 0.05) ; Use these with GUI Show, below.
Y := Y + (H * 0.2)

Gui, hh:Destroy ; Prevents duplicates of the GUI form.
Gui, hh:-MinimizeBox +alwaysOnTop ToolWindow
Gui, hh:Font, s11

Gui, hh:Add, Text, y3, Add Hotstring (don't include colons)
Gui, hh:Add, Text, ys+25 xs w30 Section, Options
Gui, hh:Add, Text, x+20 w200, %LBLhotstring%
Gui, hh:Add, Edit, ys+20 xm+10 w30 h24 vOpts Section, %myDefaultOpts%
Gui, hh:Add, Edit, x+28 w200 vHotStr, %defaultHotStr%
Gui, hh:Add, Text, xm Section, Enter Replacement String
Gui, hh:Font, s8
Gui, hh:Add, Button, ys x+20 h10 -Tabstop vBtsize gTogSize, make bigger
Gui, hh:Font, s11
Gui, hh:Add, Edit, +Wrap ys+20 xs w285 h100 vRepStr, %Clipboard%

Gui, hh:Add, Button, Section y216 x14 vBtappend, Append
Gui, hh:Add, Button, y216 x+5 vBtvalidate, Validate
Gui, hh:Add, Button, y216 x+5 vBtopen, Open
Gui, hh:Add, Button, y216 x+5 vBtcancel, Cancel

Gui, hh:Show, x%X% y%Y% h250 w312, HotStrHelper-ML ; If GUI title gets chaned, change below #IfWinActive too.
Return

TogSize:  ; "make bigger" button activates this.
GuiControlGet, Btsize
If (Btsize = "make bigger") ; This is the default state of the button.
{
 GuiControl, move, RepStr, w400 h400 ; Make replacement string box bigger.
 GuiControl, move, Btappend, y516 ; Move the buttons down.
 GuiControl, move, Btvalidate, y516
 GuiControl, move, Btopen, y516
 GuiControl, move, Btcancel, y516
 guicontrol,,Btsize, make small ; Change button to say 'make small'
 Gui, Show, h554 w428 ; Make the whole GUI form bigger.
}
else ; Button says 'make small' so...
{
 GuiControl, move, RepStr, w285 h100 ; Make box small again.
 GuiControl, move, Btappend, y216 ; Move buttons back up.
 GuiControl, move, Btvalidate, y216
 GuiControl, move, Btopen, y216
 GuiControl, move, Btcancel, y216
 guicontrol,,Btsize, make bigger ; Change button text back.
 Gui, Show, h250 w312 ; Make GUI smaller again.
}
return

#IfWinActive, HotStrHelper-ML ; Allows window-specific behavior.=======
Enter:: ; When Enter is pressed, but only in this GUI.
  GuiControlGet, hhControl, hh:Focus ; Determine which part of GUI is active.
  if (hhControl = "Edit1") || (hhControl = "Edit2") ; Edit1 is Opts box, Edit2 is the Hotstring box
  {
    Gosub, hhButtonAppend ; Opts box or HotStr box is active, so press Append button.
    return
  }
  else ; Replacement text box must be active, so...
  {
    send {Enter} ; Just normal typing; Enter yields Enter key press.
    Return
  }
Esc::
  Gui, hh:Destroy
  Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
return
#IfWinActive ; Turn off window-specific behavior.======================

hhButtonValidate: ; If either of these buttons are pressed.
hhButtonAppend:
Gui, hh:Submit, NoHide
FileRead, ThisFile, %A_ScriptName% ; Save these contents to variable 'ThisFile'.

If (Opts = "") ; If options box is empty, skip rexex check.
  validOpts = Okay.
else
{
 ; Make sure hotstring options are valid.
 NeedleRegEx := "(\*|B0|\?|SI|C|K[0-9]{1,3}|SE|X|SP|O|R|T)" ; These are in the AHK docs!!!
 WithNeedlesRemoved := RegExReplace(Opts, NeedleRegEx, "") ; Remove all valid options from var.
 If(WithNeedlesRemoved = "") ; If they were all removed...
   validOpts = Okay.
 else ; Some characters from the Options box were not recognized.
 {
     OptTips = ; Just a block text assignement to var.
     (
Don't include the colons.

..from AHK v1 docs...
 * ending char not needed
 ? trigger inside other words
 B0 no backspacing
 SI send input mode
 C case-sensitive
 K(n) set key delay
 SE send event mode
 X execute command
 SP send play mode
 O omit end char
 R send raw
 T super raw
    )
   validOpts = Invalid Hotsring Options found.`n---> %WithNeedlesRemoved%`n`n`tTips:`n%OptTips%
 }
}

; Make sure hotstring box is not empty.
validHot := "" ; Reset to empty each time.
If (HotStr = "") || (HotStr = myPrefix) || (HotStr = mySuffix) || inStr(HotStr, ":") ; || means 'or'
	validHot = Hotstring box should not be empty.`n-Don't include colons.
else ; No colons, and not empty. Good. Now check for duplicates.
 {
   Loop, parse, ThisFile, `n, `r ; Check line-by-line.
   {
    If instr(A_LoopField, HotStr . "::") ; If line contains HotStr...
       {
         validHot = DUPLICATE FOUND`nAt Line %A_Index%:`n %A_LoopField%
         break
       }
   }
 If (validHot = "") ; If variable didn't get set in loop, then no duplicates found
     validHot = Okay.
 }

; Make sure replacement string box is not empty.
If (RepStr = "") || (SubStr(RepStr, "1", "1")==":") ; If Replacement box empty, or first char is ":"
	validRep = Replacement string box should not be empty.`n-Don't include the colons.
else
	validRep = Okay.

; Concatenate the three above validity checks.
CombinedValidMsg = OPTIONS BOX `n-%validOpts%`n`nHOTSTRING BOX `n-%validHot%`n`nREPLACEMENT BOX `n-%validRep%

; Both buttons lead here, so which was pressed?
if (A_ThisLabel = "hhButtonValidate")
{
 MsgBox , 4096, VALIDATION, %CombinedValidMsg%
 return
}

if (A_ThisLabel = "hhButtonAppend")
{

   Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
  ; Check for mult-line replacment and wrap in parenths if needed.
  If inStr(RepStr,"`n")
      WholeStr :=  ":" . Opts . ":" . HotStr . "::`n(`n" . RepStr . "`n)"
  Else
      WholeStr :=  ":" . Opts . ":" . HotStr . "::" . RepStr
     ;~ MsgBox , 4096, PREVIEW, %WholeStr%

 If (validOpts = "Okay.") && (validHot = "Okay.") && (validRep = "Okay.") ; if all valid
 {
   Gui, hh:Submit, Hide
   FileAppend, `n%WholeStr%, %A_ScriptFullPath% ; 'n makes sure it goes on a new line.
   Reload
   Return
 }
 else ; The three validity checks weren't all okay, so...
 {
   MsgBox , 4100, VALIDATION, %CombinedValidMsg%`n`n####################`nContinue Anyway?
   IfMsgBox Yes
   {
     Gui, hh:Submit, Hide
     FileAppend, `n%WholeStr%, %A_ScriptFullPath% ; 'n makes sure it goes on a new line.
     Reload
     Return
   }
   else
     return
 }
}

; The other buttons.
hhButtonOpen: ; Open this file and go to the bottom so you can see your Hotstrings.
  Gui, hh:Destroy
  Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
  Edit
  WinWaitActive, %A_ScriptName% ; Wait for the script to be open in text editor.
  sleep, 250
  Send, {Ctrl Down}{End}{Ctrl Up}{Home} ; Navigate to the bottom.
return

hhButtonCancel:
  Gui, hh:Destroy
  Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
  Exit
return

;==========================
; HotStrings appended (added)
; by the script will be down there.
; Newest ones at the bottom.
;=========================


::;ottff::
(
one
two
three
four
five
)
::;tatat::This and that and the other.
::spesial::special
Miscellaneous tips/comments:
Spoiler
3-12-2023 EDIT (again) comments and code updated.
Last edited by kunkel321 on 23 Oct 2023, 18:52, edited 1 time in total.
ste(phen|ve) kunkel

User avatar
kunkel321
Posts: 1057
Joined: 30 Nov 2015, 21:19

Re: Multi-line version of Hotstring Helper

Post by kunkel321 » 20 Mar 2023, 20:30

A few more features:
* The Spell button will call a function (written by TheDewd) that uses Googe's "Did you mean...." feature to offer spelling recommendations.
* The little show pilcrow button will make pilcrows (paragraph chars) and tabs visible. (also locks the replacement box)

Image

Code: Select all

#SingleInstance, Force
; A version of Hotstring Helper that will support block multi-line replacements.
; https://www.autohotkey.com/boards/viewtopic.php?f=6&t=114688
; Needs AHK v1.1
; Please get a copy of AutoHotkey.exe and rename it to match the name of this
; script file, so that the .exe and the .ahk have the same name, in the same folder.
; DO NOT COMPILE, or the Append command won't work.
; By Kunkel321, Version 3-22-2023

!#h::   ; Activation key combo (not string) is Alt+Win+h.

AutoTrim On  ; Retain any leading and trailing whitespace on the clipboard.
ClipboardOld = %ClipboardAll% ; Save and put back later.
Clipboard =  ; Must start off blank for detection to work.
Send ^c ; Copy selected text.
ClipWait 0.3 ; Wait for clipboard to contain text.

If !InStr(Clipboard,"`n") ; Only trim NON multi line text strings.
  Clipboard := Trim(Clipboard) ; Because MS Word keeps leaving spaces.

; If white space present in selected text, probably not an Autocorrect entry.
If (InStr(Clipboard, " ") || InStr(Clipboard, "`n"))
{
 ;=======Change=options=for=multi=word=entry=options=and=trigger=strings=as=desired==============
 myDefaultOpts := ""    ; PreEnter these hotstring options; "*" = end char not needed, etc.
 myPrefix := ";"        ; Optional character that you want suggested at the beginning of each hotstring.
 addFirstLetters := 5   ; Add first letter of this many words. (5 recommended; 0 = don't use feature.)
  tooSmallLen := 2      ; Only first letters from words longer than this. (Moot if addFirstLetters = 0)
 mySuffix := ""         ; An empty string "" means don't use feature.
;===========================================================one=more=below=======================
  If (addFirstLetters > 0)
  {
    LBLhotstring := "Edit trigger string as needed"
    initials := "" ; Initials will be the first letter of each word as a hotstring suggestion.
    HotStrSug := StrReplace(Clipboard, "`n"," ") ; Unwrap, but only for hotstr suggestion.
    Loop, Parse, HotStrSug, %A_Space%
    {
      lenW := Strlen(A_LoopField)
      If (lenW > tooSmallLen) ; Check length of each word, ignore if 2 letters.
         initials :=initials . SubStr(A_LoopField, "1", "1")
      lenHS := StrLen(initials)
      If (lenHS = addFirstLetters) ; stop looping if hotstring is 5 chars long.
         break
    }
    StringLower, initials, initials
    defaultHotStr := myPrefix . initials . mySuffix ; Append preferred prefix or suffix, as defined above, to initials.
  }
  else
  {
   LBLhotstring := "Add a trigger string"
   defaultHotStr := myPrefix . mySuffix ; Use prefix and/or suffix as needed, but no initials.
  }
}
else
{
  LBLhotstring := "Add misspelled word"
  defaultHotStr = %Clipboard% ; No spaces found so assume it's a mispelling autocorrect entry: No options, no pre/suffix.

;===============Change=options=autocorrect=words=as=desired======================================
  myDefaultOpts := ""    ; PreEnter these autocorrect options; "T" = raw text mode, etc.
;================================================================================================
}

WinGetPos, X, Y, W, H, A   ; "A" to get the active window's position on screen.
X := X + (W * 0.05) ; Use these with GUI Show, below.
Y := Y + (H * 0.2)

Gui, hh:Destroy ; Prevents duplicates of the GUI form.
Gui, hh:-MinimizeBox +alwaysOnTop ToolWindow
Gui, hh:Font, s11

Gui, hh:Add, Text, y3, Add Hotstring (don't include colons)
Gui, hh:Add, Text, ys+25 xs w30 Section, Options
Gui, hh:Add, Text, x+20 w200, %LBLhotstring%
Gui, hh:Add, Edit, ys+20 xm+10 w30 h24 vOpts Section, %myDefaultOpts%
Gui, hh:Add, Edit, x+28 w215 vHotStr, %defaultHotStr%
Gui, hh:Add, Text, xm Section, Enter Replacement Text
Gui, hh:Font, s8
Gui, hh:Add, Button, ys-2 x+10 h10 -Tabstop vBtsize gTogSize, make bigger
Gui, hh:Add, Button, ys-2 x+2 h10 -Tabstop gBtsymb vTogSymb, show ¶
Gui, hh:Font, s11
Gui, hh:Add, Edit, +Wrap ys+20 xs w295 h100 vRepStr, %Clipboard%

Gui, hh:Add, Button, Section y212 x14 vBtappend, Append
Gui, hh:Add, Button, y212 x+5 vBtvalidate, Validate
Gui, hh:Add, Button, y212 x+5 vBtspell, Spell ; "Spell Check" took too much space.
Gui, hh:Add, Button, y212 x+5 vBtopen, Open
Gui, hh:Add, Button, y212 x+5 vBtcancel, Cancel

Gui, hh:Show, x%X% y%Y% h250 w322, Hotstring Helper - Multi Line ; If GUI title gets chaned, change below #IfWinActive too.
Return

TogSize:  ; "make bigger" button activates this.
GuiControlGet, Btsize
If (Btsize = "make bigger") ; This is the default state of the button.
{
 GuiControl, move, RepStr, w400 h400 ; Make replacement string box bigger.
 GuiControl, move, Btappend, y512 ; Move the buttons down.
 GuiControl, move, Btvalidate, y512
 GuiControl, move, Btspell, y512
 GuiControl, move, Btopen, y512
 GuiControl, move, Btcancel, y512
 guicontrol,,Btsize, make small ; Change button to say 'make small'
 Gui, Show, h551 w428 ; Make the whole GUI form bigger.
}
else ; Button says 'make small' so...
{
 GuiControl, move, RepStr, w295 h100 ; Make box small again.
 GuiControl, move, Btappend, y212 ; Move buttons back up.
 GuiControl, move, Btvalidate, y212
 GuiControl, move, Btspell, y212
 GuiControl, move, Btopen, y212
 GuiControl, move, Btcancel, y212
 guicontrol,,Btsize, make bigger ; Change button text back.
 Gui, Show, h250 w322 ; Make GUI smaller again.
}
return

Btsymb:
Gui, Submit, NoHide
	GuiControlGet, TogSymb
If (TogSymb = "show ¶")
{
    RepStr := StrReplace(StrReplace(RepStr,"`r`n","`n"),"`n","¶`n") ; Pilcrow for Enter
	RepStr := StrReplace(RepStr, A_Space,"•") ; middle dot for Space
	RepStr := StrReplace(RepStr, A_Tab," ? ") ; space arrow space for Tab
	GuiControl,, RepStr, %RepStr%
	GuiControl, +Readonly, RepStr,
	GuiControl,, TogSymb, hide ¶
	GuiControl, Disable, Btappend
}
else
{
	RepStr := StrReplace(RepStr,"¶`n","`n")
	RepStr := StrReplace(RepStr, "•",A_Space)
	RepStr := StrReplace(RepStr," ? ",A_Tab)
	GuiControl, -Readonly, RepStr,
	GuiControl,, RepStr, %RepStr%
	GuiControl,, TogSymb, show ¶
	GuiControl, Enable, Btappend
}
Return

#IfWinActive, Hotstring Helper - Multi Line ; Allows window-specific behavior.=======
$Enter:: ; When Enter is pressed, but only in this GUI. "$" prevents accidental Enter key loop.
  GuiControlGet, hhControl, hh:Focus ; Determine which part of GUI is active.
  GuiControlGet, TogSymb ; Get current text of button
    ; MsgBox hhControl is |%hhControl%|`n`nTogSymb is |%TogSymb%|
  if (TogSymb = "show ¶") ; Do nothing if symbol button returns this text.
  {
    ;MsgBox togSymp is 'show' so just return
    return
  }
  Else if (hhControl = "Edit1") || (hhControl = "Edit2") ; Edit1 is Opts box, Edit2 is the Hotstring box
  {
    Gosub, hhButtonAppend ; Opts box or HotStr box is active, so press Append button.
    return
  }
  else ; Replacement text box must be active, so...
  {
    send {Enter} ; Just normal typing; Enter yields Enter key press.
    Return
  }
Esc::
  Gui, hh:Destroy
  Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
return
#IfWinActive ; Turn off window-specific behavior.======================

hhButtonValidate: ; If either of these buttons are pressed.
hhButtonAppend:
Gui, hh:Submit, NoHide
FileRead, ThisFile, %A_ScriptName% ; Save these contents to variable 'ThisFile'.

If (Opts = "") ; If options box is empty, skip regxex check.
  validOpts = Okay.
else
{
 ; Make sure hotstring options are valid.
 NeedleRegEx := "(\*|B0|\?|SI|C|K[0-9]{1,3}|SE|X|SP|O|R|T)" ; These are in the AHK docs!!!
 WithNeedlesRemoved := RegExReplace(Opts, NeedleRegEx, "") ; Remove all valid options from var.
 If(WithNeedlesRemoved = "") ; If they were all removed...
   validOpts = Okay.
 else ; Some characters from the Options box were not recognized.
 {
     OptTips = ; Just a block text assignement to var.
     (
Don't include the colons.

..from AHK v1 docs...
 * ending char not needed
 ? trigger inside other words
 B0 no backspacing
 SI send input mode
 C case-sensitive
 K(n) set key delay
 SE send event mode
 X execute command
 SP send play mode
 O omit end char
 R send raw
 T super raw
    )
   validOpts = Invalid Hotsring Options found.`n---> %WithNeedlesRemoved%`n`n`tTips:`n%OptTips%
 }
}

; Make sure hotstring box is not empty.
validHot := "" ; Reset to empty each time.
If (HotStr = "") || (HotStr = myPrefix) || (HotStr = mySuffix) || inStr(HotStr, ":") ; || means 'or'
	validHot = Hotstring box should not be empty.`n-Don't include colons.
else ; No colons, and not empty. Good. Now check for duplicates.
 {
   Loop, parse, ThisFile, `n, `r ; Check line-by-line.
   {
    If instr(A_LoopField, ":" . HotStr . "::") ; If line contains HotStr...
       {
         validHot = DUPLICATE FOUND`nAt Line %A_Index%:`n %A_LoopField%
         break
       }
   }
 If (validHot = "") ; If variable didn't get set in loop, then no duplicates found
     validHot = Okay.
 }

; Make sure replacement string box is not empty.
If (RepStr = "") || (SubStr(RepStr, "1", "1")==":") ; If Replacement box empty, or first char is ":"
	validRep = Replacement string box should not be empty.`n-Don't include the colons.
else
	validRep = Okay.

; Concatenate the three above validity checks.
CombinedValidMsg = OPTIONS BOX `n-%validOpts%`n`nHOTSTRING BOX `n-%validHot%`n`nREPLACEMENT BOX `n-%validRep%

; Both buttons lead here, so which was pressed?
if (A_ThisLabel = "hhButtonValidate")
{
 MsgBox , 4096, VALIDATION, %CombinedValidMsg%
 return
}

if (A_ThisLabel = "hhButtonAppend")
{

   Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
  ; Check for mult-line replacment and wrap in parenths if needed.
  If inStr(RepStr,"`n")
      WholeStr :=  ":" . Opts . ":" . HotStr . "::`n(`n" . RepStr . "`n)"
  Else
      WholeStr :=  ":" . Opts . ":" . HotStr . "::" . RepStr
     ;~ MsgBox , 4096, PREVIEW, %WholeStr%

 If (validOpts = "Okay.") && (validHot = "Okay.") && (validRep = "Okay.") ; if all valid
 {
   Gui, hh:Submit, Hide
   FileAppend, `n%WholeStr%, %A_ScriptFullPath% ; 'n makes sure it goes on a new line.
   Reload
   Return
 }
 else ; The three validity checks weren't all okay, so...
 {
   MsgBox , 4100, VALIDATION, %CombinedValidMsg%`n`n####################`nContinue Anyway?
   IfMsgBox Yes
   {
     Gui, hh:Submit, Hide
     FileAppend, `n%WholeStr%, %A_ScriptFullPath% ; 'n makes sure it goes on a new line.
     Reload
     Return
   }
   else
     return
 }
}

hhButtonSpell: ; Spell button goes here.
Gui, hh:Submit, NoHide
If (RepStr = "")
  MsgBox Replacement Text not found.
else
{
  googleSugg := GoogleAutoCorrect(RepStr) ; Calls below function
  If (RepStr = googleSugg)
      MsgBox No suggestions found.
  Else
  {
      MsgBox 4100, Google Suggestion, %googleSugg%`n`n######################`nChange Replacement Text?
      IfMsgBox Yes
        GuiControl, Text, Edit3, %googleSugg%
      else
        return
  }
}
return
GoogleAutoCorrect(Word) ; Uses Google's "Did you mean..." feature.
{ ; function by TheDewd ;https://www.autohotkey.com/boards/viewtopic.php?f=6&t=83164
	ReqURL := "https://www.google.com/search?q=" Word
	objReq := ComObjCreate("WinHttp.WinHttpRequest.5.1")
	objReq.Open("GET", ReqURL)
	objReq.SetRequestHeader("User-Agent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)")
	objReq.Send()
	HTML := objReq.ResponseText
     ; MsgBox HTML is |%HTML%| ; It returns with a whole page of html code.
    ; These regexes filter though the html to get the google suggestion.
	RegExMatch(HTML, "value=""(.*?)""", A)
     ; MsgBox A1 is %A1%
	RegExMatch(HTML, ";spell=1.*?>(.*?)<\/a>", B)
     ; MsgBox B1 is %B1%
	return (B1 ? B1 : A1)
}

; The other buttons.
hhButtonOpen: ; Open this file and go to the bottom so you can see your Hotstrings.
  Gui, hh:Destroy
  Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
  Edit
  WinWaitActive, %A_ScriptName% ; Wait for the script to be open in text editor.
  sleep, 250
  Send, {Ctrl Down}{End}{Ctrl Up}{Home} ; Navigate to the bottom.
return

hhButtonCancel:
  Gui, hh:Destroy
  Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
  Exit
return

;==========================
; HotStrings appended (added)
; by the script will be down there.
; Newest ones at the bottom.
;=========================

::;ottff::
(
one
two
three
four
five
)
::;tatat::This and that and the other.
::spesial::special
Will do screencast later.

Oddly, the tab symbol (a right arrow) displays correctly in some ahk files that are opened in SciTE, but not in others. Dunno why.

EDIT: IMPORTANT... Or maybe not.. I added a $ before the Enter:: hotkey. Under certain circumstances the hotkey does a Send, {Enter}. The dollar sign prevents a loop. ...Though I wasn't seeing a loop with previous versions either, so I suspect that the AutoHotkey engine was stopping the loop.

EDIT 3-22-2023. Code updated. See also, discussion here:
viewtopic.php?f=76&t=115216&p=513819#p513819
ste(phen|ve) kunkel

User avatar
kunkel321
Posts: 1057
Joined: 30 Nov 2015, 21:19

Re: Multi-line version of Hotstring Helper

Post by kunkel321 » 24 Mar 2023, 15:38

This version adds a feature that folks may--or may not--want (comment-out as desired. Aprox Lines 278-288). IF you are adding an autocorrect item and have used Google to correct the spelling, THEN the script will type the corrected word into your document. The assumption here, is that you're typing in Notepad, Word, etc, and misspell a word. You then double-click the word to select it, and you activate the Hotstring Helper script. The script detects that a single word is selected, so it enters that (misspelled) word into the trigger string box and the replacement sting box. (Note that the word is still selected in your document this whole time.) IF (only if) you use the Spell button, AND if google offers a correct spelling AND if you accept that spelling correction, and then you click the Append button, THEN the form will close, and the script will again copy the misspelled word and compare it with the previouslly copied misspelled word. If they are the same, then it types in the correctly spelled replacment (in addition to appending it to your personal copy of AutoCorrect.ahk). So if you click someplace else while the HH form is still open, then the same text won't be selected, and the typing is skipped. Hopefully that makes sense.

Other changes:
-Copied text gets whitespace trimmed from edges, but only if it's not a multi-line string.
-Cleaned up some extraneous curly braces, etc.
-The "Show Symbols" feature was added so the end user could visualize line breaks, tabs, etc. The actual symbols for this can be assigned to variables. (Approx Line 124.)

EDIT 3-25-2023: Cleaned up the code a little more.
EDIT 3-26-2023: Combined a couple lines of code.

Code: Select all

#SingleInstance, Force
; A version of Hotstring Helper that will support block multi-line replacements.
; https://www.autohotkey.com/boards/viewtopic.php?f=6&t=114688
; Needs AHK v1.1
; Please get a copy of AutoHotkey.exe and rename it to match the name of this
; script file, so that the .exe and the .ahk have the same name, in the same folder.
; DO NOT COMPILE, or the Append command won't work.
; By Kunkel321, Version 3-26-2023

!#h::   ; Activation key combo (not string) is Alt+Win+h.

AutoTrim On  ; Retain any leading and trailing whitespace on the clipboard.
ClipboardOld = %ClipboardAll% ; Save and put back later.
Clipboard =  ; Must start off blank for detection to work.
Send ^c ; Copy selected text.
ClipWait 0.3 ; Wait for clipboard to contain text.

If !InStr(Clipboard,"`n") ; Only trim NON multi line text strings.
{
  preTrimBrd := Clipboard ; Use this in Appendit label to ensure misspelled word selected.
  Clipboard := Trim(Clipboard) ; Because MS Word keeps leaving spaces.
}

; If white space present in selected text, probably not an Autocorrect entry.
If (InStr(Clipboard, " ") || InStr(Clipboard, "`n"))
{
 ;=======Change=options=for=multi=word=entry=options=and=trigger=strings=as=desired==============
 myDefaultOpts := ""    ; PreEnter these hotstring options; "*" = end char not needed, etc.
 myPrefix := ";"        ; Optional character that you want suggested at the beginning of each hotstring.
 addFirstLetters := 5   ; Add first letter of this many words. (5 recommended; 0 = don't use feature.)
  tooSmallLen := 2      ; Only first letters from words longer than this. (Moot if addFirstLetters = 0)
 mySuffix := ""         ; An empty string "" means don't use feature.
;===========================================================one=more=below=======================
  If (addFirstLetters > 0)
  {
    LBLhotstring := "Edit trigger string as needed"
    initials := "" ; Initials will be the first letter of each word as a hotstring suggestion.
    HotStrSug := StrReplace(Clipboard, "`n"," ") ; Unwrap, but only for hotstr suggestion.
    Loop, Parse, HotStrSug, %A_Space%
    {
      If (Strlen(A_LoopField) > tooSmallLen) ; Check length of each word, ignore if N letters.
         initials :=initials . SubStr(A_LoopField, "1", "1")
      If (StrLen(initials) = addFirstLetters) ; stop looping if hotstring is N chars long.
         break
    }
    StringLower, initials, initials
    defaultHotStr := myPrefix . initials . mySuffix ; Append preferred prefix or suffix, as defined above, to initials.
  }
  else
  {
   LBLhotstring := "Add a trigger string"
   defaultHotStr := myPrefix . mySuffix ; Use prefix and/or suffix as needed, but no initials.
  }
}
else
{
  LBLhotstring := "Add misspelled word"
  defaultHotStr = %Clipboard% ; No spaces found so assume it's a mispelling autocorrect entry: no pre/suffix.

;===============Change=options=autocorrect=words=as=desired======================================
  myDefaultOpts := ""    ; PreEnter these autocorrect options; "T" = raw text mode, etc.
;================================================================================================
}

WinGetPos, X, Y, W, H, A   ; "A" to get the active window's position on screen.
X := X + (W * 0.05) ; Use these with GUI Show, below.
Y := Y + (H * 0.2)

Gui, hh:Destroy ; Prevents duplicates of the GUI form.
Gui, hh:-MinimizeBox +alwaysOnTop ToolWindow
Gui, hh:Font, s11

Gui, hh:Add, Text, y3, Add Hotstring (don't include colons)
Gui, hh:Add, Text, ys+25 xs w30 Section, Options
Gui, hh:Add, Text, x+20 w200, %LBLhotstring%
Gui, hh:Add, Edit, ys+20 xm+10 w30 h24 vOpts Section, %myDefaultOpts%
Gui, hh:Add, Edit, x+28 w215 vHotStr, %defaultHotStr%
Gui, hh:Add, Text, xm Section, Enter Replacement Text
Gui, hh:Font, s8
Gui, hh:Add, Button, ys-2 x+10 h10 -Tabstop vBtsize gTogSize, make bigger
Gui, hh:Add, Button, ys-2 x+2 h10 -Tabstop vBtsymb gTogSymb, show ¶ ; don't change
Gui, hh:Font, s11
Gui, hh:Add, Edit, +Wrap ys+20 xs w295 h100 vRepStr, %Clipboard%
Gui, hh:Add, Button, Section y212 x14 vBtappend, Append
Gui, hh:Add, Button, y212 x+5 vBtvalidate, Validate
Gui, hh:Add, Button, y212 x+5 vBtspell, Spell ; "Spell Check" took too much space.
Gui, hh:Add, Button, y212 x+5 vBtopen, Open
Gui, hh:Add, Button, y212 x+5 vBtcancel, Cancel

Gui, hh:Show, x%X% y%Y% h250 w322, Hotstring Helper - Multi Line ; If GUI title gets chaned, change below #IfWinActive too.
Return

TogSize:  ; "make bigger" button activates this.
GuiControlGet, Btsize
If (Btsize = "make bigger") ; This is the default state of the button.
{
   GuiControl, move, RepStr, w400 h400 ; Make replacement string box bigger.
   GuiControl, move, Btappend, y512 ; Move the buttons down.
   GuiControl, move, Btvalidate, y512
   GuiControl, move, Btspell, y512
   GuiControl, move, Btopen, y512
   GuiControl, move, Btcancel, y512
   guicontrol,,Btsize, make small ; Change button to say 'make small'
   Gui, Show, h551 w428 ; Make the whole GUI form bigger.
}
else ; Button says 'make small' so...
{
   GuiControl, move, RepStr, w295 h100 ; Make box small again.
   GuiControl, move, Btappend, y212 ; Move buttons back up.
   GuiControl, move, Btvalidate, y212
   GuiControl, move, Btspell, y212
   GuiControl, move, Btopen, y212
   GuiControl, move, Btcancel, y212
   guicontrol,,Btsize, make bigger ; Change button text back.
   Gui, Show, h250 w322 ; Make GUI smaller again.
}
return

TogSymb:
;====assign=symbols=for="show=symbols"======================================
myPilcrow := "¶"    ; Okay to change symbol here if desired.
myDot := "• "       ; adding a space allows more natural wrapping.
myTab := " >=> "    ; I can't get a right arrow symbol to display correctly.
;===========================================================================
Gui, Submit, NoHide
	GuiControlGet, Btsymb
If (Btsymb = "show ¶") ; don't change
{
    RepStr := StrReplace(StrReplace(RepStr,"`r`n","`n"),"`n",myPilcrow . "`n") ; Pilcrow for Enter
	RepStr := StrReplace(RepStr, A_Space, myDot) ; middle dot for Space
	RepStr := StrReplace(RepStr, A_Tab, myTab) ; space arrow space for Tab
	GuiControl,, RepStr, %RepStr%
	GuiControl, +Readonly, RepStr,
	GuiControl,, Btsymb, hide ¶ ; don't change
	GuiControl, Disable, Btappend
}
else
{
	RepStr := StrReplace(RepStr,myPilcrow . "`n", "`n")
	RepStr := StrReplace(RepStr, myDot, A_Space)
	RepStr := StrReplace(RepStr, myTab, A_Tab)
	GuiControl, -Readonly, RepStr,
	GuiControl,, RepStr, %RepStr%
	GuiControl,, Btsymb, show ¶ ; don't change
	GuiControl, Enable, Btappend
}
Return

#IfWinActive, Hotstring Helper - Multi Line ; Allows window-specific behavior.=======
$Enter:: ; When Enter is pressed, but only in this GUI. "$" prevents accidental Enter key loop.
  GuiControlGet, hhControl, hh:Focus ; Determine which part of GUI is active.
  GuiControlGet, Btsymb ; Get current text of button
  if (Btsymb = "show ¶") ; Do nothing if symbol button returns this text.
    return
  Else if (hhControl = "Edit1") || (hhControl = "Edit2") ; Edit1 is Opts box, Edit2 is the Hotstring box
  {
    Gosub, hhButtonAppend ; Opts box or HotStr box is active, so press Append button.
    return
  }
  else ; Replacement text box must be active, so...
  {
    send {Enter} ; Just normal typing; Enter yields Enter key press.
    Return
  }
Esc::
  Gui, hh:Destroy
  Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
return
#IfWinActive ; Turn off window-specific behavior.======================

hhButtonValidate: ; If either of these buttons are pressed.
hhButtonAppend:
Gui, hh:Submit, NoHide
FileRead, ThisFile, %A_ScriptName% ; Save these contents to variable 'ThisFile'.
; Contents of the file get used below to check for duplicate hotstrings.

If (Opts = "") ; If options box is empty, skip regxex check.
  validOpts = Okay.
else
{
 ;===== Make sure hotstring options are valid ========
 NeedleRegEx := "(\*|B0|\?|SI|C|K[0-9]{1,3}|SE|X|SP|O|R|T)" ; These are in the AHK docs!!!
 WithNeedlesRemoved := RegExReplace(Opts, NeedleRegEx, "") ; Remove all valid options from var.
 If(WithNeedlesRemoved = "") ; If they were all removed...
   validOpts = Okay.
 else ; Some characters from the Options box were not recognized.
 {
     OptTips = ; Just a block text assignement to var.
     (
Don't include the colons.

..from AHK v1 docs...
 * ending char not needed
 ? trigger inside other words
 B0 no backspacing
 SI send input mode
 C case-sensitive
 K(n) set key delay
 SE send event mode
 X execute command
 SP send play mode
 O omit end char
 R send raw
 T super raw
    )
   validOpts = Invalid Hotsring Options found.`n---> %WithNeedlesRemoved%`n`n`tTips:`n%OptTips%
 }
}

;==== Make sure hotstring box content is valid ========
validHot := "" ; Reset to empty each time.
If (HotStr = "") || (HotStr = myPrefix) || (HotStr = mySuffix) || inStr(HotStr, ":") ; || means 'or'
	validHot = Hotstring box should not be empty.`n-Don't include colons.
else ; No colons, and not empty. Good. Now check for duplicates.
   Loop, parse, ThisFile, `n, `r ; Check line-by-line.
    If instr(A_LoopField, ":" . HotStr . "::") ; If line contains HotStr...
       {
         validHot = DUPLICATE FOUND`nAt Line %A_Index%:`n %A_LoopField%
         break
       }
 If (validHot = "") ; If variable didn't get set in loop, then no duplicates found
     validHot = Okay.

;==== Make sure replacement string box content is valid ===========
If (RepStr = "") || (SubStr(RepStr, "1", "1")==":") ; If Replacement box empty, or first char is ":"
	validRep = Replacement string box should not be empty.`n-Don't include the colons.
else
	validRep = Okay.

; Concatenate the three above validity checks.
CombinedValidMsg = OPTIONS BOX `n-%validOpts%`n`nHOTSTRING BOX `n-%validHot%`n`nREPLACEMENT BOX `n-%validRep%

; Both buttons lead here, so which was pressed?
if (A_ThisLabel = "hhButtonValidate")
{
 MsgBox , 4096, VALIDATION, %CombinedValidMsg%
 return
}

if (A_ThisLabel = "hhButtonAppend")
{
  ; Check for mult-line replacment and wrap in parenths if needed.
  If inStr(RepStr,"`n")
      WholeStr :=  ":" . Opts . ":" . HotStr . "::`n(`n" . RepStr . "`n)"
  Else
      WholeStr :=  ":" . Opts . ":" . HotStr . "::" . RepStr

 If (validOpts = "Okay.") && (validHot = "Okay.") && (validRep = "Okay.") ; if all valid
  {
    gosub, Appendit
    return
  }
 else ; The three validity checks weren't all okay, so...
 {
   MsgBox , 4100, VALIDATION, %CombinedValidMsg%`n`n####################`nContinue Anyway?
   IfMsgBox Yes ; User clicked Yes.
    {
    gosub, Appendit
    return
  }
   else
     return
 }
}

Appendit: ; Not associated with a button.  This is reached from above Gosub commands.
   Gui, hh:Submit, Hide
   FileAppend, `n%WholeStr%, %A_ScriptFullPath% ; 'n makes sure it goes on a new line.
    If (LBLhotstring = "Add misspelled word") && (ReplaceMisspell = googleSugg)
    ; If it is an autocorrect entry and a google suggestion was accepted...
    { ; Comment-out this If { } block to prevent typing of corrected word.
      Sleep, 250
      Clipboard =
      Send, ^c
      If (Clipboard = preTrimBrd) ; Compare originally-captured word with just now copied.
        Send, %ReplaceMisspell%
    }
    Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
   Reload
   Return

hhButtonSpell: ; Spell button goes here.
Gui, hh:Submit, NoHide
If (RepStr = "")
  MsgBox Replacement Text not found.
else
{
  googleSugg := GoogleAutoCorrect(RepStr) ; Calls below function
  If (RepStr = googleSugg)
      MsgBox No suggestions found.
  Else
  {
      MsgBox 4100, Google Suggestion, %googleSugg%`n`n######################`nChange Replacement Text?
      IfMsgBox Yes
      {
        GuiControl, Text, Edit3, %googleSugg%
        ReplaceMisspell := googleSugg
      }
      else
        return
  }
}
return

GoogleAutoCorrect(Word) ; Uses Google's "Did you mean..." feature.
{ ; function by TheDewd ;https://www.autohotkey.com/boards/viewtopic.php?f=6&t=83164
	ReqURL := "https://www.google.com/search?q=" Word
	objReq := ComObjCreate("WinHttp.WinHttpRequest.5.1")
	objReq.Open("GET", ReqURL)
	objReq.SetRequestHeader("User-Agent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)")
	objReq.Send()
	HTML := objReq.ResponseText
  	RegExMatch(HTML, "value=""(.*?)""", A) ; These regexes filter though the html to get the google suggestion.
   	RegExMatch(HTML, ";spell=1.*?>(.*?)<\/a>", B)
   	return (B1 ? B1 : A1)
}

; The other buttons.
hhButtonOpen: ; Open this file and go to the bottom so you can see your Hotstrings.
  Gui, hh:Destroy
  Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
  Edit
  WinWaitActive, %A_ScriptName% ; Wait for the script to be open in text editor.
  sleep, 250
  Send, {Ctrl Down}{End}{Ctrl Up}{Home} ; Navigate to the bottom.
return

hhButtonCancel:
  Gui, hh:Destroy
  Clipboard = %ClipboardOld%  ; Restore previous contents of clipboard.
  Exit
return

;==========================
; HotStrings appended (added)
; by the script will be down there.
; Newest ones at the bottom.
;=========================
ste(phen|ve) kunkel

User avatar
kunkel321
Posts: 1057
Joined: 30 Nov 2015, 21:19

Re: Multi-line version of Hotstring Helper

Post by kunkel321 » 06 Aug 2023, 13:23

A preliminary version for AHK v2. Still tweaking it... Learning v2 as I go...

+Adds ability to define how large the large size is (when toggled).
+Adds a place to put comments.
-Removed "appear over active window" feature.
-Removed "automatically replace the google-corrected misspelling" as you create the new autocorrect entry.

Code: Select all

#SingleInstance
#Requires AutoHotkey v2.0
; A version of Hotstring Helper that will support block multi-line replacements.
; https://www.autohotkey.com/boards/viewtopic.php?f=6&t=114688
; Needs AHK v2. Partly auto-converted from v1, partly rewritten.
; Please get a copy of AutoHotkey.exe (v2) and rename it to match the name of this
; script file, so that the .exe and the .ahk have the same name, in the same folder.
; DO NOT COMPILE, or the Append command won't work.
; By Kunkel321, Version 8-6-2023

calColor := "F0F8FF" ; "F0F8FF" is light blue
FontColor := "003366" ; "003366" is dark blue
Global hFactor := 0 ; Don't change here.  Change in TogSize() function.
Global wFactor := 0 ; Don't change here.  Change in TogSize() function.
FormName := "Hotstring Helper -- Muli-Line"

hh := Gui('', FormName)
hh.Opt("-MinimizeBox +alwaysOnTop")
hh.BackColor := calColor
hh.SetFont("s11 c" . FontColor)
; -----  Trigger string parts
hh.AddText('y4 w30', 'Options')
hh.AddText('vTrigStrLbl x+20 w250', 'Trigger String')
hh.AddEdit('vMyDefaultOpts yp+20 xm+10 w30 h24')
DefHotStr := hh.AddEdit('vDefHotStr x+28 w' . wFactor + 250, '')
; ----- Replacement string parts
hh.AddText('xm', 'Enter Replacement String')
hh.SetFont('s9')
hh.AddButton('vSizeTog x+5 yp-5 h8 +notab', 'Make Bigger').OnEvent("Click", TogSize)
hh.AddButton('vSymTog x+5 h8 +notab', '+ Symbols').OnEvent("Click", TogSym)
hh.SetFont('s11')
;RepStr := hh.AddEdit('vRepStr +Wrap yp+25 xs h' . hFactor + 100 . ' w' . wFactor + 320, '')
RepStr := hh.AddEdit('vRepStr +Wrap y+1 xs h' . hFactor + 100 . ' w' . wFactor + 320, '')
ComLbl := hh.AddText('xm y' . hFactor + 182, 'Enter Comment')
ComStr := hh.AddEdit('vComStr xs y' . hFactor + 200 . ' w' . wFactor + 315)
; ---- Buttons
(ButApp := hh.AddButton('xm y' . hFactor + 234, '&Append')).OnEvent("Click", hhButtonAppend)
(ButVal := hh.AddButton('+notab x+5 y' . hFactor + 234, '&Validate')).OnEvent("Click", hhButtonValidate)
(ButSpell := hh.AddButton('+notab x+5 y' . hFactor + 234, '&Spell')).OnEvent("Click", hhButtonSpell)
(ButOpen := hh.AddButton('+notab x+5 y' . hFactor + 234, '&Open')).OnEvent("Click", hhButtonOpen)
(ButCancel := hh.AddButton('+notab x+5 y' . hFactor + 234, '&Cancel')).OnEvent("Click", hhButtonCancel)

!#h::   ; Activation key combo (not string) is Win+h.
{
  MyDefaultOpts := ""
  DefaultHotStr := ""
  DefHotStr := ""
  LBLhotstring := ""
  SizeTog := ""
  addFirstLetters := ""
  tooSmallLen := ""
  Global myPrefix := ""
  Global mySuffix := ""
  initials := ""
  HotStrSug := ""
  Global tMyDefaultOpts := ""
  Global tDefHotStr := ""
  Global tRepStr := ""
  Global tComStr := ""
  Global ClipboardOld := ClipboardAll() ; Save and put back later.
  A_Clipboard := ""  ; Must start off blank for detection to work.
  Send("^c") ; Copy selected text.
  Errorlevel := !ClipWait(0.3) ; Wait for clipboard to contain text.

  If !InStr(A_Clipboard, "`n") ; Only trim NON multi line text strings.
    A_Clipboard := Trim(A_Clipboard) ; Because MS Word keeps leaving spaces.

  ; If white space present in selected text, probably not an Autocorrect entry.
  If (InStr(A_Clipboard, " ") || InStr(A_Clipboard, "`n"))
  {
   ;=======Change=options=for=MULTI=word=entry=options=and=trigger=strings=as=desired==============
   MyDefaultOpts := ""    ; PreEnter these multi-word hotstring options; "*" = end char not needed, etc.
   myPrefix := ";"        ; Optional character that you want suggested at the beginning of each hotstring.
   addFirstLetters := 5   ; Add first letter of this many words. (5 recommended; 0 = don't use feature.)
    tooSmallLen := 2      ; Only first letters from words longer than this. (Moot if addFirstLetters = 0)
   mySuffix := ""         ; An empty string "" means don't use feature.
  ;===========================================================one=more=below=======================
    If (addFirstLetters > 0)
    { LBLhotstring := "Edit trigger string as needed"
      initials := "" ; Initials will be the first letter of each word as a hotstring suggestion.
      HotStrSug := StrReplace(A_Clipboard, "`n", " ") ; Unwrap, but only for hotstr suggestion.
      Loop Parse, HotStrSug, A_Space
      { If (Strlen(A_LoopField) > tooSmallLen) ; Check length of each word, ignore if N letters.
           initials :=initials . SubStr(A_LoopField, ("1")<1 ? ("1")-1 : ("1"), "1")
        If (StrLen(initials) = addFirstLetters) ; stop looping if hotstring is N chars long.
           break
      }
      initials := StrLower(initials)
      DefaultHotStr := myPrefix . initials . mySuffix ; Append preferred prefix or suffix, as defined above, to initials.
    }
    else
    {LBLhotstring := "Add a trigger string"
     DefaultHotStr := myPrefix . mySuffix ; Use prefix and/or suffix as needed, but no initials.
    }
  }
  Else If (A_Clipboard = "")
      LBLhotstring := "Add a trigger string"
  else
  { LBLhotstring := "Add misspelled word"
    DefaultHotStr := A_Clipboard ; No spaces found so assume it's a mispelling autocorrect entry: no pre/suffix.
    ;===============Change=options=AUTOCORRECT=words=as=desired======================================
    myDefaultOpts := ""    ; PreEnter these (single-word) autocorrect options; "T" = raw text mode, etc.
    ;================================================================================================
  }
  hh['MyDefaultOpts'].value := MyDefaultOpts
  hh['TrigStrLbl'].value := LBLhotstring
  hh['DefHotStr'].value := DefaultHotStr
  hh['RepStr'].value := A_Clipboard
  hh.Show('Autosize')
} ; bottom of hotkey function

TogSize(*)
{
	If (hh['SizeTog'].text = "Make Bigger") {
       hh['SizeTog'].text := "Make Smaller"
      hFactor := 200 ; <------------------------------  User changes as desired.
      wFactor := 200 ; <------------------------------  User changes as desired.

      DefHotStr.Move(,, wFactor + 250,)
      RepStr.Move(,, wFactor + 320, hFactor + 100)
      ComLbl.Move(, hFactor + 182,,)
      ComStr.move(, hFactor + 200, wFactor + 315,)
      ButApp.Move(, hFactor + 234,,)
      ButVal.Move(, hFactor + 234,,)
      ButSpell.Move(, hFactor + 234,,)
      ButOpen.Move(, hFactor + 234,,)
      ButCancel.Move(, hFactor + 234,,)
      hh.Show('Autosize Center')
      return
    }

    If (hh['SizeTog'].text = "Make Smaller") {
      hh['SizeTog'].text := "Make Bigger"
      hFactor := 0
      wFactor := 0
       DefHotStr.Move(,, wFactor + 250,)
      RepStr.Move(,, wFactor + 320, hFactor + 100)
      ComLbl.Move(, hFactor + 182,,)
      ComStr.move(, hFactor + 200, wFactor + 315,)
      ButApp.Move(, hFactor + 234,,)
      ButVal.Move(, hFactor + 234,,)
      ButSpell.Move(, hFactor + 234,,)
      ButOpen.Move(, hFactor + 234,,)
      ButCancel.Move(, hFactor + 234,,)
      hh.Show('Autosize')
      return
    }
}

TogSym(*)
{ ;====assign=symbolss=for="show symb"=button=================================
  myPilcrow := "¶"    ; Okay to change symb here if desired.
  myDot := "• "       ; adding a space allows more natural wrapping.
  myTab := "►"
  ;===========================================================================

  If (hh['SymTog'].text = "+ Symbols") {
     hh['SymTog'].text := "- Symbols"
     RepStr := hh['RepStr'].text
     RepStr := StrReplace(StrReplace(RepStr, "`r`n", "`n"), "`n", myPilcrow . "`n") ; Pilcrow for Enter
     RepStr := StrReplace(RepStr, A_Space, myDot) ; middle dot for Space
     RepStr := StrReplace(RepStr, A_Tab, myTab) ; space arrow space for Tab
     hh['RepStr'].value := RepStr
     hh['RepStr'].Opt("+Readonly")
     ButApp.Enabled := false
     hh.Show('Autosize')
     return
  }
  If (hh['SymTog'].text = "- Symbols") {
    hh['SymTog'].text := "+ Symbols"
    RepStr := hh['RepStr'].text
    RepStr := StrReplace(RepStr, myPilcrow . "`r", "`r") ; Have to use `r ... weird.
	RepStr := StrReplace(RepStr, myDot, A_Space)
	RepStr := StrReplace(RepStr, myTab, A_Tab)
    hh['RepStr'].value := RepStr
    hh['RepStr'].Opt("-Readonly")
    ButApp.Enabled := true
    hh.Show('Autosize')
    return
  }
}

#HotIf WinActive(FormName, ) ; Allows window-specific behavior.=============================
{
  $Enter:: ; When Enter is pressed, but only in this GUI. "$" prevents accidental Enter key loop.
  {
    If (hh['SymTog'].text = "Hide Symb")
      return
    Else if RepStr.Focused {
      Send("{Enter}") ; Just normal typing; Enter yields Enter key press.
      Return
    }
    Else {
      hhButtonAppend() ; Replacement box not focused, so press Append button.
      return
    }
  }
  Esc::
  {
    hh.Hide()
    A_Clipboard := ClipboardOld
  }
}
#HotIf ; Turn off window-specific behavior.======================

hhButtonAppend(*)
{
  tMyDefaultOpts := hh['MyDefaultOpts'].text
  tDefHotStr := hh['DefHotStr'].text
  tRepStr := hh['RepStr'].text

  ValidationFunction(tMyDefaultOpts, tDefHotStr, tRepStr)
  If Not InStr(CombinedValidMsg, "-Okay.",,, 3)
  {    ; Msg doesn't have three occurrences of "-Okay."
    msgResult := MsgBox(CombinedValidMsg "`n`n####################`nContinue Anyway?", "VALIDATION", "OC 4096" )
    if (msgResult = "OK") {
       Appendit(tMyDefaultOpts, tDefHotStr, tRepStr) ; not valid, but user chose to continue anyway
       return
     }
    else
       return ; not valid, and user cancelled
  }
  else { ; no validation problems found
    Appendit(tMyDefaultOpts, tDefHotStr, tRepStr)
    return
 }
}

hhButtonValidate(*)
{
  tMyDefaultOpts := hh['MyDefaultOpts'].text
  tDefHotStr := hh['DefHotStr'].text
  tRepStr := hh['RepStr'].text
  ValidationFunction(tMyDefaultOpts, tDefHotStr, tRepStr)
  MsgBox("Validation Results`n#################`n" . CombinedValidMsg,, 4096)
  Return
}

ValidationFunction(tMyDefaultOpts, tDefHotStr, tRepStr)
{
  Global CombinedValidMsg
  ThisFile := Fileread(A_ScriptName) ; Save these contents to variable 'ThisFile'.
 ; ThisFile := Fileread("S:\AutoHotkey\MasterScript\MasterScript.ahk") ; <---- CHANGE later
  If (tMyDefaultOpts = "") ; If options box is empty, skip regxex check.
    validOpts := "Okay."
  else { ;===== Make sure hotstring options are valid ========
   NeedleRegEx := "(\*|B0|\?|SI|C|K[0-9]{1,3}|SE|X|SP|O|R|T)" ; These are in the AHK docs I swear!!!
   WithNeedlesRemoved := RegExReplace(tMyDefaultOpts, NeedleRegEx, "") ; Remove all valid options from var.

  If(WithNeedlesRemoved = "") ; If they were all removed...
     validOpts := "Okay."
   else { ; Some characters from the Options box were not recognized.
     OptTips := " ; Just a block text assignement to var
       (
  Don't include the colons.
  ..from AHK v1 docs...
   * - ending char not needed
   ? - trigger inside other words
   B0 - no backspacing
   SI - send input mode
   C - case-sensitive
   K(n) - set key delay
   SE - send event mode
   X - execute command
   SP - send play mode
   O - omit end char
   R - send raw
   T - super raw
      )"
     validOpts := "Invalid Hotsring Options found.`n---> " . WithNeedlesRemoved . "`n`n`tTips:`n" . OptTips
   }
  }
    ;==== Make sure hotstring box content is valid ========
  validHot := "" ; Reset to empty each time.
  If (tDefHotStr = "") || (tDefHotStr = myPrefix) || (tDefHotStr = mySuffix) || InStr(tDefHotStr, ":")
      validHot := "HotString box should not be empty.`n-Don't include colons."
  else ; No colons, and not empty. Good. Now check for duplicates.
     Loop Parse, ThisFile, "`n", "`r" ; Check line-by-line.
      If instr(A_LoopField, ":" . tDefHotStr . "::") { ; If line contains tDefHotStr...
           validHot := "DUPLICATE FOUND`nAt Line " . A_Index . ":`n " . A_LoopField
           break
         }
   If (validHot = "") ; If variable didn't get set in loop, then no duplicates found
       validHot := "Okay."
  ;==== Make sure replacement string box content is valid ===========
  If (tRepStr = "") || (SubStr(tRepStr, ("1")<1 ? ("1")-1 : ("1"), "1")==":") ; If Replacement box empty, or first char is ":"
      validRep := "Replacement string box should not be empty.`n-Don't include the colons."
  else
      validRep := "Okay."
  ; Concatenate the three above validity checks.
  CombinedValidMsg := "OPTIONS BOX `n-" . validOpts . "`n`nHOTSTRING BOX `n-" . validHot . "`n`nREPLACEMENT BOX `n-" . validRep
  Return CombinedValidMsg ; return result for use is Append or Validation functions.
} ; end of validation func

Appendit(tMyDefaultOpts, tDefHotStr, tRepStr)
{
  WholeStr := ""
  tMyDefaultOpts := hh['MyDefaultOpts'].text
  tDefHotStr := hh['DefHotStr'].text
  tRepStr := hh['RepStr'].text
  tComStr := hh['ComStr'].text
  If (tComStr != "")
    tComStr := " `; " . tComStr
  If InStr(tRepStr, "`n") {
    WholeStr :=  ":" . tMyDefaultOpts . ":" . tDefHotStr . "::" . tComStr . "`n(`n" . tRepStr . "`n)"
  }
  Else {
    WholeStr :=  ":" . tMyDefaultOpts . ":" . tDefHotStr . "::" . tRepStr . tComStr
  }
  FileAppend("`n" WholeStr, A_ScriptFullPath) ; 'n makes sure it goes on a new line.
  Reload() ; relaod the script so the new hotstring will be ready for use.
}

hhButtonSpell(*) ; Called is "Spell" because "Spell Check" is too long.
{
  tRepStr := hh['RepStr'].text
  If (tRepStr = "")
    MsgBox("Replacement Text not found.")
  else {
    googleSugg := GoogleAutoCorrect(tRepStr) ; Calls below function
    If (tRepStr = googleSugg)
        MsgBox("No suggestions found.")
    Else {
      msgResult := MsgBox(googleSugg "`n`n######################`nChange Replacement Text?", "Google Suggestion", "OC 4096")
      if (msgResult = "OK")
        hh['RepStr'].value := googleSugg
      else
      return
    }
  }
}

GoogleAutoCorrect(word)
{ ; Original by TheDewd, converted to v2 by Mikeyww.
  ; autohotkey.com/boards/viewtopic.php?f=82&t=120143
 objReq := ComObject('WinHttp.WinHttpRequest.5.1')
 objReq.Open('GET', 'https://www.google.com/search?q=' word)
 objReq.SetRequestHeader('User-Agent'
  , 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)')
 objReq.Send(), HTML := objReq.ResponseText
 RegExMatch(HTML, 'value="(.*?)"', &A)
 RegExMatch(HTML, ';spell=1.*?>(.*?)<\/a>', &B)
 Return B[1] || A[1]
}

hhButtonOpen(*)
{  ; Open this file and go to the bottom so you can see your Hotstrings.
  hh.Hide()
  A_Clipboard := ClipboardOld  ; Restore previous contents of clipboard.
  Edit()
  WinWaitActive(A_ScriptName) ; Wait for the script to be open in text editor.
  Sleep(250)
  Send("{Ctrl Down}{End}{Ctrl Up}{Home}") ; Navigate to the bottom.
}

hhButtonCancel(*)
{
  hh.Hide()
  A_Clipboard := ClipboardOld  ; Restore previous contents of clipboard.
}

;==========================
; HotStrings appended (added)
; by the script will be below.
; Newest ones at the bottom.
;=========================

Image
ste(phen|ve) kunkel

User avatar
kunkel321
Posts: 1057
Joined: 30 Nov 2015, 21:19

Re: Multi-line version of Hotstring Helper

Post by kunkel321 » 28 Feb 2024, 17:24

In case anyone is watching this tread... There is a new version of this tool here
viewtopic.php?f=83&t=120220&p=559946#p559328
And it got a new name. It is now "HotString Helper 2.0." It is currently bundled with AutoCorrect for v2.
ste(phen|ve) kunkel

Post Reply

Return to “Scripts and Functions (v1)”