Compatibility: The Importance of Backporting My set-up Functionality That Was/Should Be Backported AutoHotkey v1/v2: Key Changes
Two-Way Compatibility Examples Print a string literal Print a variable's contents Define a hotkey Create a blank string with capacity 1000 chars Set a property's value dynamically Get a property's value dynamically Create a static variable (but not when the script starts) A direct equivalent to StringSplit Receive a variadic parameter and discard it Modify a global variable in a hotkey subroutine Make some function variables local and the rest global File loop For loop variable scope bug Create a class instance Continuation section: verbatim text Continuation section: verbatim text (no trim) Continuation section: verbatim text (smart trim) Continuation section: expression with variables Continuation section: expression with variables (no trim) Continuation section: expression with variables (smart trim) Function with compatibility workarounds: ComObject Function with compatibility workarounds: SubStr
Benefits of total backward compatibility: ● Start with a v1 script, that you intend to be a v1/v2 script. You can run a test suite on the joint v1/v2 script, using the v1 and v2 exes, until all tests are passed. ● ... Worst-case scenario, if total backward compatibility is unavailable: you try your best to get the script as v2-ready as possible while still working in v1, then you take the 'leap of faith'/a 'leap in the dark', you do all the required edits that you think you need to make to run it in v2, you may need to modify the scripts for many hours/days/weeks just to get it to run without throwing, meanwhile you don't have a working script. ● You only need to maintain one version of each script. (And you halve the amount of disk space needed on the hard drive/server.) ● You can write/maintain core libraries that work readily in both versions. E.g. you might want to fix or update someone else's old v1 script, and you can do it with your joint v1/v2 toolset. ● If people ask for your v1 script to be converted to v2, you can make a v1/v2 script. One script to maintain, both audiences served. ● If people ask for your v2 script to be converted to v1, if you write code in a style that is mostly/fully two-way compatible, you can just make a few tweaks to make it a v1/v2 script. (If I write code for v2, the first question is often: can you convert it to v1.) ● Some organisations only have permission for certain versions of the exe. ● Older versions may be able to work on older OSes. ● Older versions may have different syntax, making transpilation to/from other languages easier. ● Often using the new version has all the conversion costs, but no new feature benefits. Since there's no benefit to updating, at least make it easy. ● Users are at different levels, make it easy for them to update. ● A trivial amount of developer time, developing the exe, e.g. one week, to backport v2 functionality to v1; years/decades of time is saved for the users, writing scripts. ● Even relatively competent developers can think that backporting enough functionality for reasonable/complete two-way compatibility is 'impossible', when really it's trivial or fairly trivial. Useful features for compatibility: ● An #If directive, the code is visible/invisible depending on the exe version number. ● A well-thought out syntax (e.g. #Requires) for specifying version ranges for the exe. E.g. specify that the script should run in v1.1.37.02..<v2.0 or v2.0..<v3.0, else throw. ● There are certain things in a programming language you want to get right first time, version number functionality is one of them. ● (It's good to maintain support for the old version for say 5 years, because you don't immediately know all the functionality you should backport.) What can be backported via custom scripts (AHK code): ● functions ● classes What can be backported by modifying the exe (C++ code): ● operators ● directives ● A_ variables ● keywords ● syntax (e.g. 'MyObj.%MyProp%') ● custom class functionality to distinguish between dot syntax ('MyObj.MyProp') and subscript syntax ('MyObj[MyKey]') when objects receive values
My team. I am gathering a consortium of programmers to work on AutoHotkey. This includes a few tweaks to AutoHotkey v1. Because of the quality of the team, this could become the official AutoHotkey v1.2. Note: the intention is to only do backports, however, people would be welcome to work on custom AutoHotkey forks. Note: this team could implement and review PRs for AutoHotkey v2 e.g. array Join()/Reverse()/Sort() methods. (I.e. a high standard of code, prior to submission for the main AHK developer.) My set-up. Currently, I have all my scripts as two versions, simultaneously AHK v1 and AHK v2. I converted literally everything, all the scripts that I still use, including any libraries written by others. The AHK v1 scripts are the master scripts, 'pseudo-AHK v2' scripts, using a backport (polyfill) library for functions/GUIs. (The 'AHK v2 functions for AHK v1' backport library.) They are 99% AHK v2 code with occasional bits of code to make them two-way compatible. Those scripts are auto-converted to AHK v2, the modifications are: the bits I can't currently make two-way compatible, and some bits that could be made two-way compatible but where it's inconvenient (e.g. Loop File). I almost always run the AHK v2 versions of those scripts, but for one or two I use the v1 versions. The ideal set-up. The ideal is that new functionality is backported to the old version, and you modify your script so that it works in both the old and new versions. With a few source code backports, I could have 99% of my scripts be two-way compatible (one file for both versions), with one or two scripts having two separate versions (for AHK v1/v2), e.g. to store custom classes. (Perhaps if an AHK v1 'instantiate object instance without the new keyword' directive was added, it could be 99.9%/100% two-way compatibility.) However, the source code backports would have to be *official*, otherwise only I could properly use those scripts, and no-one else. Converting scripts. When converting my scripts from AHK v1 to AHK v2, I would explore ways to convert, to see if any problems could not be overcome with clever code, and would thus require updating the AutoHotkey source code. From this emerged the 'Two-Way Compatibility Examples' below. Terminology. Some alternative phrases for 'two-way compatibility', are apparently: ● Cross-version compatible code. ● Dual-compatible code. ● Polyglot script. Code that can be run in different programming languages. Two-way compatibility can be obtained by: ● Forwards compatibility (old exe handles new code). (Not applicable here, but in some software/hardware contexts, newer incompatible features are simply ignored.) ● Backwards compatibility (new exe handles old code). (I.e. the exe can handle old and new functionality.) ● Adding functionality to the old version, through polyfill functions, or adding to the source code. ● Writing code that avoids using newer incompatible syntax. ● Clever workarounds. Code that can sometimes be hacky. ● #If functionality can greatly simplify this. (If '#If' syntax is established in v1 of a programming language, it makes compatibility trivial.)
Functionality that was backported: ● operators !==, >>> and >>>= (by jeeswg) ● A_Clipboard, A_InitialWorkingDir (by jeeswg) ● File.Handle (by jeeswg) ● IsSet() (by jeeswg) ● StrReplace(), StrSplit() (by lexikos) ● A_Args (by lexikos) ● := in function headers (by lexikos) ⭐⭐Functionality that should be backported: ● backport '&' in function headers, equivalent to 'ByRef' (workaround: assign to object property) ● backport 'catch as oError', equivalent to 'catch oError' ● backport #HotIf, equivalent to #If (note: may need optimisation: '#HotIf WinActive(...)') (workaround: use Hotkey() function) ● backport ObjFromPtr()/ObjPtr() (and ObjFromPtrAddRef()/ObjPtrAddRef()), this would allow Object() to be modified, thus Object methods could be custom backported as AHK code ● backport '!~=' operator (not RegExMatch()) ● backport '=>' (fat-arrow functions) (perhaps in a limited form: e.g. 1-liners: oFunc := v => v*2) ⭐⭐Functionality that should be added to AHK v2, then backported as source code: ● A_VP1 or A_VerPart1 or equivalent: e.g. '#Include MyScript v%A_VP1%.ahk' to include a 'v1' or 'v2' script (also: A_ScriptNameNoExt and A_AhkDir/A_AhkName/A_AhkNameNoExt) ● print function (or operator) (the sheer utility/convenience of 'print myvar') (Ruby even has 'p myvar') ● add `x## / `u#### / `u{#####} to escape chars (equivalent to \x and \u in other languages) ● faster Loop Files via FindExInfoBasic/FIND_FIRST_EX_LARGE_FETCH ● ToNumber()/ToString() (this would allow you to create 'null' and built-in variable polyfills e.g. 'A_MyVar') (a possible solution) ⭐⭐Functionality that should be added to AHK v2, then backported as AHK code (or source code): ● array methods: Join(), Reverse(), Sort(), IndexOf()/Index() (also: Map/Filter) ● Range/StrSplit-LoopParse/LoopFile/LoopReg functionality that can be used with for loops Functionality that should be restored: ● 'AHK ANSI' exe, using UTF-8 strings, this would be a way to reduce memory consumption for text-heavy scripts ● make A_ScriptName read-only again (perhaps add A_ScriptTitle) ● minor: AHK v1 NumPut parameter order (perhaps add NumPutPairs for the v2 order) (it's easier to modify code between NumGet/classic NumPut, and array support could be added: NumPut(ValueArray, Address, Offset, TypeArray)) ● minor: make // do floor division (perhaps add ~/ to do trunc division) Functionality that can be backported via AutoHotkey code: ● 'AHK v2 functions for AHK v1' backports functions and classes (e.g. array/buffer/case-insensitive map/object/GUI). See AutoHotkey - Bazzle. Functionality ported to JavaScript: ● Many AutoHotkey functions have been ported to JavaScript. See: JEE.js.
Syntax/functionality that was removed from AutoHotkey (available in v1, not available in v2): ● gosub (workaround: use functions) ● dynamic variable creation (workaround: define the variables before use) ● 'local' by itself (workaround: explicitly list local function variables, as you would in JavaScript) ● Random() no longer accepts a seed value ● Progress/SplashImage/SplashTextOn removed ● Array min index is always 1, it cannot be specified ● ObjGetAddress() (workaround: perhaps use buffers) ● 'static MyVar := 123' no longer assigns when the script starts (only when the function is called) ('Local static variables are initialized if and when execution reaches them, instead of being executed in linear order before the auto-execute section begins.') ● 'A_IsUnicode (v2 is always Unicode; it can be replaced with StrLen(Chr(0xFFFF))' (note: A_IsUnicode is blank string, not 0, in AHK ANSI) ● 'Multi-dimension array hacks were removed' ● 'Scripts are no longer automatically included from the function library (Lib) folders when a function call is present without a definition' ('the function library auto-include mechanism was removed') Syntax/functionality that was added to AutoHotkey (new for v2): ● '&' instead of 'ByRef' in function headers ● 'MyObj.%MyProp%' ● '=>' (fat-arrow functions) ● oFunc(oParams*) (in AHK v1, had to do: oFunc.Call(oParams*), which still works) (functions stored as variables) ● hotkeys require braces, and have their own scope (preventing accidental variable modification by other hotkeys) ● fewer silent errors when using built-in object methods ● for loop variable scope fixed (no leaking) ● MsgBox/InputBox: major changes ● create a class instance like so: oObj := MyClass() (the 'new' keyword was removed) ('The new operator has been removed. Instead, just omit the operator, as in MyClass().') ● 'Quoted literal strings can be written with "double" or 'single' quote marks' ● 'The operators &&, ||, and and or yield whichever value determined the result' (before, they returned 1 or 0, i.e. true/false) Some gotchas (parameter reordering): ● 'For a multi-statement expression, the result of the last statement is returned.' (not listed in 'v2-changes.htm') (e.g. myvar := (1, 2, 3) returns 1 in AHK v1, 3 in AHK v2) ● 'NumPut's parameters were reordered' ● 'StrReplace now has a CaseSense parameter in place of OutputVarCount, which is moved one parameter to the right, with Limit following it.' Source: Changes from v1.1 to v2.0 | AutoHotkey v2 Some gotchas: ● '&var is now the reference operator, which is used with all ByRef and OutputVar parameters' ● '*/ can now be placed at the end of a line to end a multi-line comment' ● 'Chr(0) returns a string of length 1, containing a binary zero.' ● 'CoordMode defaults to Client (added in v1.1.05) instead of Window.' ● 'Else can now be used with loops of any type and Catch ... the interpretation of Else may differ from previous versions when used without braces' ● 'InStr now searches right-to-left when Occurrence is negative' (and other changes) ● 'PixelSearch and PixelGetColor use RGB values instead of BGR' ● 'Title matching mode defaults to 2 instead of 1.' ● arrays: 'Length is now a property.' ● ComObjCreate/ComObject: 'ComObject(CLSID) creates a ComObject; i.e. this is the new ComObjCreate.' ● ComObject/ComValue: 'If you are updating old code and get a TypeError due to passing an Integer to ComObject, it's likely that you should be calling ComValue instead.' ● ControlXXX/PostMessage/SendMessage: 'Blank values are invalid. Functions never default to the target window's topmost control.' ('ControlID parameter used by the Control functions, SendMessage and PostMessage') ● InStr/SubStr/RegExMatch/RegExReplace: 'A negative StartingPos for InStr, SubStr, RegExMatch and RegExReplace is interpreted as a position from the end. Position -1 is the last character and position 0 is invalid (whereas in v1, position 0 was the last character).' ● maps/arrays: 'HasKey was renamed to Has.' ● Send: 'SendMode defaults to Input instead of Event.' Source: Changes from v1.1 to v2.0 | AutoHotkey v2
Below are examples of code for: ● AHK v1 only (red) ● AHK v2 only (yellow) ● AHK v1/v2 two-way compatible (green) Where it says 'requires backport lib', that is a reference to 'AHK v2 functions for AHK v1 (and GUI class)'. See: AutoHotkey - Bazzle. While converting scripts, every once in a while, I would discover new conversion issues, that I would eventually solve. Elegant two-way compatible workarounds, meant the issue could be 'one and done', with the option to remove backwards compatibility tricks at a later date if desired. I discovered all of the solutions in the examples below. Absolutely pivotal for me, were these ideas: ● The hotkey trick ('return' for v1/braces for v2). For hotkey subroutines. ● The VarRef trick ('vIsV1?vOutput:&vOutput'). For functions that accept VarRef parameters. ● Global/local variable workarounds. ● IsSet() workarounds. For this problem: in AHK v1, 'Local static variables are initialized ... in linear order before the auto-execute section begins'. ● Continuation section workarounds (verbatim text and expressions with variables).
Code: Select all
;note: AHK v1: { } is redundant ('return' is necessary)
;note: AHK v2: 'return' is redundant ({ } is necessary)
^q::
{
MyFunc()
return
}
Code: Select all
vOutput := ""
VarSetCapacity(vOutput, 1000 << !!A_IsUnicode) ;note: bitshift by 1 (double) if Unicode, bitshift by 0 (multiply by 1) if ANSI
MsgBox, % VarSetCapacity(vOutput) ;byte count
Code: Select all
vOutput := ""
VarSetStrCapacity(&vOutput, 1000)
MsgBox(VarSetStrCapacity(&vOutput)) ;char count
Code: Select all
;note: AHK v1: requires backport lib (for MsgBox/VarSetStrCapacity)
vIsV1 := (InStr(A_AhkVersion, "1.") == 1)
vOutput := ""
VarSetStrCapacity(vIsV1?vOutput:&vOutput, 1000)
MsgBox(VarSetStrCapacity(vIsV1?vOutput:&vOutput)) ;char count
Code: Select all
;note: AHK v1: properties/keys are equivalent (basic object is array/map/object)
oObj := {}
oObj[vProp] := vValue
Code: Select all
oObj := {}
oObj.%vProp% := vValue
;an alternative:
oObj := {}
oObj.DefineProp(vProp, {Value:vValue})
Code: Select all
;note: AHK v1: requires backport lib (for .DefineProp, currently via a custom class)
oObj := {}
oObj.DefineProp(vProp, {Value:vValue})
Code: Select all
;note: AHK v1: properties/keys are equivalent (basic object is array/map/object)
vValue := oObj[vProp]
Code: Select all
;note: AHK v1: requires backport lib (for .GetOwnPropDesc, currently via a custom class)
vValue := oObj.GetOwnPropDesc(vProp).Value
Code: Select all
;WARNING: this returns a blank string:
;this fails because, in AHK v1, static lines in functions are run when the script starts,
;... vNum1 := MyFunc2() causes MyFunc2() to be called, but 'static vNum2 := 123' hasn't been executed yet,
;... so vNum2's value is unset, and a blank string is returned:
;see v1/v2 code for a workaround using IsSet():
;note: this is one of the key reasons I created the IsSet() function:
MyFunc1()
{
static vNum1 := MyFunc2()
return vNum1
}
MyFunc2()
{
static vNum2 := 123
return vNum2
}
MsgBox % MyFunc1()
;workaround:
MyFunc1()
{
static vNum1 := MyFunc2()
return vNum1
}
MyFunc2()
{
if !IsSet(vNum2)
vNum2 := 123
return vNum2
}
MsgBox, % MyFunc1()
Code: Select all
;note: AHK v2: works as expected, outputs 123
MyFunc1()
{
static vNum1 := MyFunc2()
return vNum1
}
MyFunc2()
{
static vNum2 := 123
return vNum2
}
MsgBox(MyFunc1())
Code: Select all
;note: AHK v1: requires backport lib (for MsgBox)
MyFunc1()
{
static vNum1 := MyFunc2()
return vNum1
}
MyFunc2()
{
if !IsSet(vNum2)
vNum2 := 123
return vNum2
}
MsgBox(MyFunc1())
Code: Select all
;note: StringSplit is available in AHK v1, but not AHK v2:
;note: most things in AHK v1 can be converted as one-liners,
;... but converting StringSplit is one of the few things that is more involved:
;WARNING: AHK v1: variables are created dynamically: 'myvar0' for the count, 'myvar1'/'myvar2'/'myvar3' etc for the output parts:
vText := "abc,def,ghi"
vSep := ","
StringSplit, vTextNew, vText, % vSep
MsgBox, % vTextNew0 " " vTextNew1 " " vTextNew2 " " vTextNew3 ;3 abc def ghi
;workaround:
vText := "abc,def,ghi"
vSep := ","
Loop Parse, vText, % ","
{
vTextNew0 := A_Index
vTextNew%A_Index% := A_LoopField
}
MsgBox, % vTextNew0 " " vTextNew1 " " vTextNew2 " " vTextNew3 ;3 abc def ghi
;an alternative:
vText := "abc,def,ghi"
vSep := ","
oArray := StrSplit(vText, vSep)
MsgBox, % oArray.Length() " " oArray[1] " " oArray[2] " " oArray[3] ;3 abc def ghi
Code: Select all
;workaround:
vText := "abc,def,ghi"
vSep := ","
vTextNew1 := vTextNew2 := vTextNew3 := ""
Loop Parse, vText, ","
{
vTextNew0 := A_Index
vTextNew%A_Index% := A_LoopField
}
MsgBox(vTextNew0 " " vTextNew1 " " vTextNew2 " " vTextNew3) ;3 abc def ghi
;an alternative:
vText := "abc,def,ghi"
vSep := ","
oArray := StrSplit(vText, vSep)
MsgBox(oArray.Length " " oArray[1] " " oArray[2] " " oArray[3]) ;3 abc def ghi
Code: Select all
;workaround:
;note: AHK v1: requires backport lib (for MsgBox)
vIsV1 := (InStr(A_AhkVersion, "1.") == 1)
vText := "abc,def,ghi"
vSep := ","
vTextNew1 := vTextNew2 := vTextNew3 := ""
vTemp := vIsV1 ? vSep : "vSep"
Loop Parse, vText, %vTemp%
{
vTextNew0 := A_Index
vTextNew%A_Index% := A_LoopField
}
MsgBox(vTextNew0 " " vTextNew1 " " vTextNew2 " " vTextNew3) ;3 abc def ghi
;an alternative:
;note: AHK v1: requires backport lib (for .Length/MsgBox)
vText := "abc,def,ghi"
vSep := ","
oArray := StrSplit(vText, vSep)
MsgBox(oArray.Length " " oArray[1] " " oArray[2] " " oArray[3]) ;3 abc def ghi
Code: Select all
MyFunc1(*)
{
}
MyFunc2(vArg1, vArg2, vArg3, *)
{
}
;an alternative:
MyFunc1(oParams*)
{
}
MyFunc2(vArg1, vArg2, vArg3, oParams*)
{
}
Code: Select all
^q::
(!IsSet(vNum)) && (vNum := 0)
vNum++
MsgBox, % vNum
return
;an alternative:
;note: 'global vNum' is redundant
^q::
global vNum
(!IsSet(vNum)) && (vNum := 0)
vNum++
MsgBox, % vNum
return
Code: Select all
;note: AHK v1: { } is redundant ('return' is necessary)
;note: AHK v2: 'return' is redundant ({ } is necessary)
;note: AHK v1: 'global vNum' is redundant
;note: AHK v1: requires backport lib (for MsgBox)
^q::
{
global vNum
(!IsSet(vNum)) && (vNum := 0)
vNum++
MsgBox(vNum)
return
}
Code: Select all
vVar1 := "g"
vVar4 := "g"
MyFunc()
{
global ;all variables are global apart from the local variables below:
local vVar1, vVar2, vVar3
vVar1 := "l"
MsgBox, % vVar1 vVar4 ;lg
vVar4 := "f"
}
MyFunc()
MsgBox, % vVar1 vVar4 ;gf
;an alternative:
vVar1 := "g"
vVar4 := "g"
MyFunc()
{
local vVar1, vVar2, vVar3
vVar1 := "l"
MsgBox, % vVar1 vVar4 ;lg
vVar4 := "f"
}
MyFunc()
MsgBox, % vVar1 vVar4 ;gf
Code: Select all
vVar1 := "g"
vVar4 := "g"
MyFunc()
{
global ;all variables are global apart from the local variables below:
local vVar1, vVar2, vVar3
vVar1 := "l"
MsgBox(vVar1 vVar4) ;lg
vVar4 := "f"
}
MyFunc()
MsgBox(vVar1 vVar4) ;gf
;an alternative:
;vVar1 := "g"
;vVar4 := "g"
;MyFunc()
;{
; local vVar1, vVar2, vVar3
; vVar1 := "l"
; MsgBox(vVar1 vVar4) ;lg
; vVar4 := "f" ;this would throw
;}
;MyFunc()
;MsgBox(vVar1 vVar4) ;gg
Code: Select all
;note: AHK v1: requires backport lib (for MsgBox)
vVar1 := "g"
vVar4 := "g"
MyFunc()
{
global ;all variables are global apart from the local variables below:
local vVar1, vVar2, vVar3
vVar1 := "l"
MsgBox(vVar1 vVar4) ;lg
vVar4 := "f"
}
MyFunc()
MsgBox(vVar1 vVar4) ;gf
Code: Select all
vFilePattern := A_Desktop "\*"
vMode := "F"
Loop Files, % vFilePattern, % vMode
{
vPath := A_LoopFileFullPath
MsgBox, % vPath
break ;just show first file
}
Code: Select all
vFilePattern := A_Desktop "\*"
vMode := "F"
Loop Files, vFilePattern, vMode
{
vPath := A_LoopFileFullPath
MsgBox(vPath)
break ;just show first file
}
Code: Select all
;note: AHK v1: requires backport lib (for MsgBox)
vIsV1 := (InStr(A_AhkVersion, "1.") == 1)
vFilePattern := A_Desktop "\*"
vMode := "F"
vTemp1 := vIsV1 ? vFilePattern : "vFilePattern"
vTemp2 := vIsV1 ? vMode : "vMode"
Loop Files, %vTemp1%, %vTemp2%
{
vPath := A_LoopFileFullPath
MsgBox(vPath)
break ;just show first file
}
Code: Select all
;note: bug occurs in AHK v1
oArray := [11, 22, 33]
for vKey, vValue in oArray
{} ;noop
MsgBox, % vKey " " vValue ;3 33 (should be unset)
Code: Select all
;note: bug does not occur in AHK v2
;oArray := [11, 22, 33]
;for vKey, vValue in oArray
; {} ;noop
;MsgBox(vKey " " vValue) ;throws
Code: Select all
;note: bug occurs in AHK v1
;note: bug does not occur in AHK v2
;note: a similar bug happens in Python ('it's not a bug, it's a feature')
Code: Select all
class MyClass
{
MyMethod()
{
MsgBox, % "hello"
}
}
oObj := new MyClass
oObj.MyMethod()
oObj2 := new MyClass()
oObj2.MyMethod()
Code: Select all
;note: AHK v1: requires backport lib (for MsgBox)
;lines for AHK v1 only, a function to create a class instance:
/*
MyClass()
{
return new MyClass
}
*/
class MyClass
{
MyMethod()
{
MsgBox("hello")
}
}
oObj := MyClass()
oObj.MyMethod()
Code: Select all
;WARNING: verbatim apart from double quotes, which are doubled up:
vText := " ;continuation section
(` Join`r`n
!""#$%&'()*+,-./0123456789:;<=>?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
`abcdefghijklmnopqrstuvwxyz{|}~
)"
A_Clipboard := vText
MsgBox, % vText
;an alternative:
vText = ;continuation section
(` % Join`r`n
!"#$%&'()*+,-./0123456789:;<=>?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
`abcdefghijklmnopqrstuvwxyz{|}~
)
A_Clipboard := vText
MsgBox, % vText
Code: Select all
vText := " ;continuation section
(` Join`r`n
!"#$%&'()*+,-./0123456789:;<=>?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
`abcdefghijklmnopqrstuvwxyz{|}~
)"
A_Clipboard := vText
MsgBox(vText)
Code: Select all
;note: AHK v1: requires backport lib (for MsgBox)
;WARNING: verbatim apart from double quotes, which are doubled up:
vIsV1 := (InStr(A_AhkVersion, "1.") == 1)
vDQ := Chr(34)
vText := " ;continuation section
(` Join`r`n
!""#$%&'()*+,-./0123456789:;<=>?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
`abcdefghijklmnopqrstuvwxyz{|}~
)"
if !vIsV1
vText := StrReplace(vText, vDQ vDQ, vDQ)
A_Clipboard := vText
MsgBox(vText)
Code: Select all
;WARNING: verbatim apart from double quotes, which are doubled up:
vText := " ;continuation section
(` Join`r`n LTrim0
if vCond
{
vVar1 := ""abc""
vVar2 := ""def""
vVar3 := ""ghi""
}
)"
A_Clipboard := vText
MsgBox, % vText
;an alternative:
;WARNING: must maintain the blank line:
vText = ;continuation section
(` Join`r`n LTrim0 C
if vCond
{
vVar1 := "abc"
vVar2 := "def"
vVar3 := "ghi"
}
)
vText := LTrim(vText, "`r`n")
A_Clipboard := vText
MsgBox, % vText
Code: Select all
vText := " ;continuation section
(` Join`r`n LTrim0
if vCond
{
vVar1 := "abc"
vVar2 := "def"
vVar3 := "ghi"
}
)"
A_Clipboard := vText
MsgBox(vText)
Code: Select all
;note: AHK v1: requires backport lib (for MsgBox)
;WARNING: verbatim apart from double quotes, which are doubled up:
vIsV1 := (InStr(A_AhkVersion, "1.") == 1)
vDQ := Chr(34)
vText := " ;continuation section
(` Join`r`n LTrim0
if vCond
{
vVar1 := ""abc""
vVar2 := ""def""
vVar3 := ""ghi""
}
)"
if !vIsV1
vText := StrReplace(vText, vDQ vDQ, vDQ)
A_Clipboard := vText
MsgBox(vText)
Code: Select all
;WARNING: verbatim apart from double quotes, which are doubled up:
vText := " ;continuation section
(` Join`r`n LTrim0
if vCond
{
vVar1 := ""abc""
vVar2 := ""def""
vVar3 := ""ghi""
}
)"
vText := MyLTrimSmart(vText)
A_Clipboard := vText
MsgBox, % vText
;an alternative:
;WARNING: must maintain the blank line:
vText = ;continuation section
(` Join`r`n LTrim0
if vCond
{
vVar1 := "abc"
vVar2 := "def"
vVar3 := "ghi"
}
)
vText := LTrim(vText, "`r`n")
vText := MyLTrimSmart(vText)
A_Clipboard := vText
MsgBox, % vText
MyLTrimSmart(vText)
{
local vLen, vTemp
vTemp := StrReplace(vText, "`r")
vTemp := RegExReplace(vTemp, "`nm)^(\t*).*$", "$1")
Sort, vTemp
vLen := StrLen(StrSplit(vTemp, "`n")[1])
return RegExReplace(vText, "`am)^\t{" vLen "}")
}
Code: Select all
;note: in AHK v2, continuation sections use 'smart' trim by default:
vText := " ;continuation section
(` Join`r`n
if vCond
{
vVar1 := "abc"
vVar2 := "def"
vVar3 := "ghi"
}
)"
A_Clipboard := vText
MsgBox(vText)
;an alternative:
vText := " ;continuation section
(` Join`r`n LTrim0
if vCond
{
vVar1 := "abc"
vVar2 := "def"
vVar3 := "ghi"
}
)"
vText := MyLTrimSmart(vText)
A_Clipboard := vText
MsgBox(vText)
MyLTrimSmart(vText)
{
local vLen, vTemp
vTemp := StrReplace(vText, "`r")
vTemp := RegExReplace(vTemp, "`nm)^(\t*).*$", "$1")
vLen := StrLen(StrSplit(Sort(vTemp), "`n")[1])
return RegExReplace(vText, "`am)^\t{" vLen "}")
}
Code: Select all
;note: AHK v1: requires backport lib (for MsgBox/Sort)
;WARNING: verbatim apart from double quotes, which are doubled up:
vIsV1 := (InStr(A_AhkVersion, "1.") == 1)
vDQ := Chr(34)
vText := " ;continuation section
(` Join`r`n LTrim0
if vCond
{
vVar1 := ""abc""
vVar2 := ""def""
vVar3 := ""ghi""
}
)"
if !vIsV1
vText := StrReplace(vText, vDQ vDQ, vDQ)
vText := MyLTrimSmart(vText)
A_Clipboard := vText
MsgBox(vText)
MyLTrimSmart(vText)
{
local vLen, vTemp
vTemp := StrReplace(vText, "`r")
vTemp := RegExReplace(vTemp, "`nm)^(\t*).*$", "$1")
vLen := StrLen(StrSplit(Sort(vTemp), "`n")[1])
return RegExReplace(vText, "`am)^\t{" vLen "}")
}
Code: Select all
vNum := 456
vText := ;continuation section
(
"vVar1 := 123
vVar2 := " vNum "
vVar3 := 789
)"
MsgBox, % vText
Code: Select all
vNum := 456
vText := ;continuation section
(
"vVar1 := 123
vVar2 := " vNum "
vVar3 := 789
)"
MsgBox(vText)
Code: Select all
;note: AHK v1: requires backport lib (for MsgBox)
vNum := 456
vText := ;continuation section
(
"vVar1 := 123
vVar2 := " vNum "
vVar3 := 789
)"
MsgBox(vText)
Code: Select all
vNum := 456
vText := ;continuation section
(Join`r`n LTrim0
"
if vCond
{
vVar1 := 123
vVar2 := " vNum "
vVar3 := 789
}
)"
vText := LTrim(vText, "`r`n")
A_Clipboard := vText
MsgBox, % vText
Code: Select all
vNum := 456
vText := ;continuation section
(Join`r`n LTrim0
"
if vCond
{
vVar1 := 123
vVar2 := " vNum "
vVar3 := 789
}
)"
vText := LTrim(vText, "`r`n")
A_Clipboard := vText
MsgBox(vText)
Code: Select all
;note: AHK v1: requires backport lib (for MsgBox)
vNum := 456
vText := ;continuation section
(Join`r`n LTrim0
"
if vCond
{
vVar1 := 123
vVar2 := " vNum "
vVar3 := 789
}
)"
vText := LTrim(vText, "`r`n")
A_Clipboard := vText
MsgBox(vText)
Code: Select all
vNum := 456
vText := ;continuation section
(Join`r`n LTrim0
"
if vCond
{
vVar1 := 123
vVar2 := " vNum "
vVar3 := 789
}
)"
vText := LTrim(vText, "`r`n")
vText := MyLTrimSmart(vText)
A_Clipboard := vText
MsgBox, % vText
MyLTrimSmart(vText)
{
local vLen, vTemp
vTemp := StrReplace(vText, "`r")
vTemp := RegExReplace(vTemp, "`nm)^(\t*).*$", "$1")
Sort, vTemp
vLen := StrLen(StrSplit(vTemp, "`n")[1])
return RegExReplace(vText, "`am)^\t{" vLen "}")
}
Code: Select all
;note: in AHK v2, continuation sections use 'smart' trim by default:
;note: the indentation of the opening double quote line affects 'smart' LTrim:
vNum := 456
vText := ;continuation section
(Join`r`n
"
if vCond
{
vVar1 := 123
vVar2 := " vNum "
vVar3 := 789
}
)"
vText := LTrim(vText, "`r`n")
A_Clipboard := vText
MsgBox(vText)
;an alternative:
vNum := 456
vText := ;continuation section
(Join`r`n LTrim0
"
if vCond
{
vVar1 := 123
vVar2 := " vNum "
vVar3 := 789
}
)"
vText := LTrim(vText, "`r`n")
vText := MyLTrimSmart(vText)
A_Clipboard := vText
MsgBox(vText)
MyLTrimSmart(vText)
{
local vLen, vTemp
vTemp := StrReplace(vText, "`r")
vTemp := RegExReplace(vTemp, "`nm)^(\t*).*$", "$1")
vLen := StrLen(StrSplit(Sort(vTemp), "`n")[1])
return RegExReplace(vText, "`am)^\t{" vLen "}")
}
Code: Select all
;note: AHK v1: requires backport lib (for MsgBox/Sort)
vNum := 456
vText := ;continuation section
(Join`r`n LTrim0
"
if vCond
{
vVar1 := 123
vVar2 := " vNum "
vVar3 := 789
}
)"
vText := LTrim(vText, "`r`n")
vText := MyLTrimSmart(vText)
A_Clipboard := vText
MsgBox(vText)
MyLTrimSmart(vText)
{
local vLen, vTemp
vTemp := StrReplace(vText, "`r")
vTemp := RegExReplace(vTemp, "`nm)^(\t*).*$", "$1")
vLen := StrLen(StrSplit(Sort(vTemp), "`n")[1])
return RegExReplace(vText, "`am)^\t{" vLen "}")
}
Code: Select all
oDict := MyComObject("Scripting.Dictionary")
MyComObject(oParams*)
{
static vIsV1
static ComObjCreate := 0 ;dummy assignment so AHK v2 doesn't complain
if !IsSet(vIsV1)
vIsV1 := (InStr(A_AhkVersion, "1.") == 1)
if vIsV1
return ComObjCreate(oParams*) ;do not convert (must be ComObjCreate, not ComObject) ;[FOR V1]
else
return ComObject(oParams*)
}
Code: Select all
;note: AHK v1: requires backport lib (for MsgBox)
MsgBox(MySubStr("abcdefghij", -3)) ;hij
MySubStr(vText, vPos, vLen:="")
{
static vIsV1 := !!SubStr(1, 0)
if (vPos == 0)
return ""
if vIsV1 && (vPos <= -1)
vPos++
if (vLen == "")
return SubStr(vText, vPos)
return SubStr(vText, vPos, vLen)
}