String.ahk
Heavily based on String Things by tidbit.
A compilation of useful string methods. Also lets strings be treated as objects.
Examples:
Code: Select all
"test".Length ; returns 4
str := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
MsgBox("First character: " str[1])
MsgBox("Substring for characters 2-7: " str[2,7])
MsgBox("Substring starting from character 10: " str[10,0])
MsgBox("Reversed: " str.Reverse())
MsgBox("Word wrapped: `n" str.WordWrap())
MsgBox("Concatenate: " "; ".Concat("First", "second", 123))
Code: Select all
/*
Name: String.ahk
Version 0.14 (23.03.2023)
Created: 27.08.22
Author: Descolada
Credit:
tidbit --- Author of "String Things - Common String & Array Functions", from which
I copied/based a lot of methods
Contributors: Axlefublr, neogna2
Contributors to "String Things": AfterLemon, Bon, Lexikos, MasterFocus, Rseding91, Verdlin
Description:
A compilation of useful string methods. Also lets strings be treated as objects.
These methods cannot be used as stand-alone. To do that, you must add another argument
'string' to the function and replace all occurrences of 'this' with 'string'.
.-==========================================================================-.
| Properties |
|============================================================================|
| String.Length |
| .IsDigit |
| .IsXDigit |
| .IsAlpha |
| .IsUpper |
| .IsLower |
| .IsAlnum |
| .IsSpace |
| .IsTime |
|============================================================================|
| Methods |
|============================================================================|
| Native functions as methods: |
| String.ToUpper() |
| .ToLower() |
| .ToTitle() |
| .Split([Delimiters, OmitChars, MaxParts]) |
| .Replace(Needle [, ReplaceText, CaseSense, &OutputVarCount, Limit]) |
| .Trim([OmitChars]) |
| .LTrim([OmitChars]) |
| .RTrim([OmitChars]) |
| .Compare(comparison [, CaseSense]) |
| .Sort([, Options, Function]) |
| .Format([Values...]) |
| .Find(Needle [, CaseSense, StartingPos, Occurrence]) |
| .SplitPath() => returns object {FileName, Dir, Ext, NameNoExt, Drive}| |
| .RegExMatch(needleRegex, &match?, startingPos?) |
| .RegExMatchAll(needleRegex, startingPos?) |
| .RegExReplace(needle, replacement?, &count?, limit?, startingPos?) |
| |
| String[n] => gets nth character |
| String[i,j] => substring from i to j |
| for [index,] char in String => loops over the characters in String |
| String.Length |
| String.Count(searchFor) |
| String.Insert(insert, into [, pos]) |
| String.Delete(string [, start, length]) |
| String.Overwrite(overwrite, into [, pos]) |
| String.Repeat(count) |
| Delimeter.Concat(words*) |
| |
| String.LineWrap([column:=56, indentChar:=""]) |
| String.WordWrap([column:=56, indentChar:=""]) |
| String.ReadLine(line [, delim:="`n", exclude:="`r"]) |
| String.DeleteLine(line [, delim:="`n", exclude:="`r"]) |
| String.InsertLine(insert, into, line [, delim:="`n", exclude:="`r"]) |
| |
| String.Reverse() |
| String.Contains(needle1 [, needle2, needle3...]) |
| String.RemoveDuplicates([delim:="`n"]) |
| String.LPad(count) |
| String.RPad(count) |
| |
| String.Center([fill:=" ", symFill:=0, delim:="`n", exclude:="`r", width]) |
| String.Right([fill:=" ", delim:="`n", exclude:="`r"]) |
'-==========================================================================-'
*/
Class String2 {
static __New() {
; Add String2 methods and properties into String object
__ObjDefineProp := Object.Prototype.DefineProp
for __String2_Prop in String2.OwnProps()
if SubStr(__String2_Prop, 1, 2) != "__"
__ObjDefineProp(String.Prototype, __String2_Prop, String2.GetOwnPropDesc(__String2_Prop))
__ObjDefineProp(String.Prototype, "__Item", {get:(args*)=>String2.__Item[args*]})
__ObjDefineProp(String.Prototype, "__Enum", {call:String2.__Enum})
}
static __Item[args*] {
get {
if args.length = 2
return SubStr(args[1], args[2], 1)
else {
len := StrLen(args[1])
if args[2] < 0
args[2] := len+args[2]+1
if args[3] < 0
args[3] := len+args[3]+1
if args[3] >= args[2]
return SubStr(args[1], args[2], args[3]-args[2]+1)
else
return SubStr(args[1], args[3], args[2]-args[3]+1).Reverse()
}
}
}
static __Enum(varCount) {
pos := 0, len := StrLen(this)
EnumElements(&char) {
char := StrGet(StrPtr(this) + 2*pos, 1)
return ++pos <= len
}
EnumIndexAndElements(&index, &char) {
char := StrGet(StrPtr(this) + 2*pos, 1), index := ++pos
return pos <= len
}
return varCount = 1 ? EnumElements : EnumIndexAndElements
}
; Native functions implemented as methods for the String object
static Length => StrLen(this)
static WLength => (RegExReplace(this, "s).", "", &i), i)
static ULength => StrLen(RegExReplace(this, "s)((?>\P{M}(\p{M}|\x{200D}))+\P{M})|\X", "_"))
static IsDigit => IsDigit(this)
static IsXDigit => IsXDigit(this)
static IsAlpha => IsAlpha(this)
static IsUpper => IsUpper(this)
static IsLower => IsLower(this)
static IsAlnum => IsAlnum(this)
static IsSpace => IsSpace(this)
static IsTime => IsTime(this)
static ToUpper() => StrUpper(this)
static ToLower() => StrLower(this)
static ToTitle() => StrTitle(this)
static Split(args*) => StrSplit(this, args*)
static Replace(args*) => StrReplace(this, args*)
static Trim(args*) => Trim(this, args*)
static LTrim(args*) => LTrim(this, args*)
static RTrim(args*) => RTrim(this, args*)
static Compare(args*) => StrCompare(this, args*)
static Sort(args*) => Sort(this, args*)
static Format(args*) => Format(this, args*)
static Find(args*) => InStr(this, args*)
static SplitPath() => (SplitPath(this, &a1, &a2, &a3, &a4, &a5), {FileName: a1, Dir: a2, Ext: a3, NameNoExt: a4, Drive: a5})
/**
* Returns the match object
* @param needleRegex *String* What pattern to match
* @param startingPos *Integer* Specify a number to start matching at. By default, starts matching at the beginning of the string
* @returns {Object}
*/
static RegExMatch(needleRegex, &match?, startingPos?) => (RegExMatch(this, needleRegex, &match, startingPos?), match)
/**
* Returns all RegExMatch results in an array: [RegExMatchInfo1, RegExMatchInfo2, ...]
* @param needleRegEx *String* The RegEx pattern to search for.
* @param startingPosition *Integer* If StartingPos is omitted, it defaults to 1 (the beginning of haystack).
* @returns {Array}
*/
static RegExMatchAll(needleRegEx, startingPosition := 1) {
out := []
While startingPosition := RegExMatch(this, needleRegEx, &outputVar, startingPosition)
out.Push(outputVar), startingPosition += outputVar[0] ? StrLen(outputVar[0]) : 1
return out
}
/**
* Uses regex to perform a replacement, returns the changed string
* @param needleRegex *String* What pattern to match.
* This can also be a Array of needles (and replacement a corresponding array of replacement values),
* in which case all of the pairs will be searched for and replaced with the corresponding replacement.
* replacement should be left empty, outputVarCount will be set to the total number of replacements, limit is the maximum
* number of replacements for each needle-replacement pair.
* @param replacement *String* What to replace that match into
* @param outputVarCount *VarRef* Specify a variable with a `&` before it to assign it to the amount of replacements that have occured
* @param limit *Integer* The maximum amount of replacements that can happen. Unlimited by default
* @param startingPos *Integer* Specify a number to start matching at. By default, starts matching at the beginning of the string
* @returns {String} The changed string
*/
static RegExReplace(needleRegex, replacement?, &outputVarCount?, limit?, startingPos?) {
if IsObject(needleRegex) {
out := this, count := 0
for i, needle in needleRegex {
out := RegExReplace(out, needle, IsSet(replacement) ? replacement[i] : unset, &count, limit?, startingPos?)
if IsSet(outputVarCount)
outputVarCount += count
}
return out
}
return RegExReplace(this, needleRegex, replacement?, &outputVarCount?, limit?, startingPos?)
}
/**
* Add character(s) to left side of the input string.
* example: "aaa".LPad("+", 5)
* output: +++++aaa
* @param padding Text you want to add
* @param count How many times do you want to repeat adding to the left side.
* @returns {String}
*/
static LPad(padding, count:=1) {
str := this
if (count>0) {
Loop count
str := padding str
}
return str
}
/**
* Add character(s) to right side of the input string.
* example: "aaa".RPad("+", 5)
* output: aaa+++++
* @param padding Text you want to add
* @param count How many times do you want to repeat adding to the left side.
* @returns {String}
*/
static RPad(padding, count:=1) {
str := this
if (count>0) {
Loop count
str := str padding
}
return str
}
/**
* Count the number of occurrences of needle in the string
* input: "12234".Count("2")
* output: 2
* @param needle Text to search for
* @param caseSensitive
* @returns {Integer}
*/
static Count(needle, caseSensitive:=False) {
StrReplace(this, needle,, caseSensitive, &count)
return count
}
/**
* Duplicate the string 'count' times.
* input: "abc".Repeat(3)
* output: "abcabcabc"
* @param count *Integer*
* @returns {String}
*/
static Repeat(count) => StrReplace(Format("{:" count "}",""), " ", this)
/**
* Reverse the string.
* @returns {String}
*/
static Reverse() {
DllCall("msvcrt\_wcsrev", "str", str := this, "CDecl str")
return str
}
static WReverse() {
str := this, out := "", m := ""
While str && (m := Chr(Ord(str))) && (out := m . out)
str := SubStr(str,StrLen(m)+1)
return out
}
/**
* Insert the string inside 'insert' into position 'pos'
* input: "abc".Insert("d", 2)
* output: "adbc"
* @param insert The text to insert
* @param pos *Integer*
* @returns {String}
*/
static Insert(insert, pos:=1) {
Length := StrLen(this)
((pos > 0)
? pos2 := pos - 1
: (pos = 0
? (pos2 := StrLen(this), Length := 0)
: pos2 := pos
)
)
output := SubStr(this, 1, pos2) . insert . SubStr(this, pos, Length)
if (StrLen(output) > StrLen(this) + StrLen(insert))
((Abs(pos) <= StrLen(this)/2)
? (output := SubStr(output, 1, pos2 - 1)
. SubStr(output, pos + 1, StrLen(this))
)
: (output := SubStr(output, 1, pos2 - StrLen(insert) - 2)
. SubStr(output, pos - StrLen(insert), StrLen(this))
)
)
return output
}
/**
* Replace part of the string with the string in 'overwrite' starting from position 'pos'
* input: "aaabbbccc".Overwrite("zzz", 4)
* output: "aaazzzccc"
* @param overwrite Text to insert.
* @param pos The position where to begin overwriting. 0 may be used to overwrite at the very end, -1 will offset 1 from the end, and so on.
* @returns {String}
*/
static Overwrite(overwrite, pos:=1) {
if (Abs(pos) > StrLen(this))
return ""
else if (pos>0)
return SubStr(this, 1, pos-1) . overwrite . SubStr(this, pos+StrLen(overwrite))
else if (pos<0)
return SubStr(this, 1, pos) . overwrite . SubStr(this " ",(Abs(pos) > StrLen(overwrite) ? pos+StrLen(overwrite) : 0), Abs(pos+StrLen(overwrite)))
else if (pos=0)
return this . overwrite
}
/**
* Delete a range of characters from the specified string.
* input: "aaabbbccc".Delete(4, 3)
* output: "aaaccc"
* @param start The position where to start deleting.
* @param length How many characters to delete.
* @returns {String}
*/
static Delete(start:=1, length:=1) {
if (Abs(start) > StrLen(this))
return ""
if (start>0)
return SubStr(this, 1, start-1) . SubStr(this, start + length)
else if (start<=0)
return SubStr(this " ", 1, start-1) SubStr(this " ", ((start<0) ? start-1+length : 0), -1)
}
/**
* Wrap the string so each line is never more than a specified length.
* input: "Apples are a round fruit, usually red".LineWrap(20, "---")
* output: "Apples are a round f
* ---ruit, usually red"
* @param column Specify a maximum length per line
* @param indentChar Choose a character to indent the following lines with
* @returns {String}
*/
static LineWrap(column:=56, indentChar:="") {
CharLength := StrLen(indentChar)
, columnSpan := column - CharLength
, Ptr := A_PtrSize ? "Ptr" : "UInt"
, UnicodeModifier := 2
, VarSetStrCapacity(&out, (finalLength := (StrLen(this) + (Ceil(StrLen(this) / columnSpan) * (column + CharLength + 1))))*2)
, A := StrPtr(out)
Loop parse, this, "`n", "`r" {
if ((FieldLength := StrLen(ALoopField := A_LoopField)) > column) {
DllCall("RtlMoveMemory", "Ptr", A, "ptr", StrPtr(ALoopField), "UInt", column * UnicodeModifier)
, A += column * UnicodeModifier
, NumPut("UShort", 10, A)
, A += UnicodeModifier
, Pos := column
While (Pos < FieldLength) {
if CharLength
DllCall("RtlMoveMemory", "Ptr", A, "ptr", StrPtr(indentChar), "UInt", CharLength * UnicodeModifier)
, A += CharLength * UnicodeModifier
if (Pos + columnSpan > FieldLength)
DllCall("RtlMoveMemory", "Ptr", A, "ptr", StrPtr(ALoopField) + (Pos * UnicodeModifier), "UInt", (FieldLength - Pos) * UnicodeModifier)
, A += (FieldLength - Pos) * UnicodeModifier
, Pos += FieldLength - Pos
else
DllCall("RtlMoveMemory", "Ptr", A, "ptr", StrPtr(ALoopField) + (Pos * UnicodeModifier), "UInt", columnSpan * UnicodeModifier)
, A += columnSpan * UnicodeModifier
, Pos += columnSpan
NumPut("UShort", 10, A)
, A += UnicodeModifier
}
} else
DllCall("RtlMoveMemory", "Ptr", A, "ptr", StrPtr(ALoopField), "UInt", FieldLength * UnicodeModifier)
, A += FieldLength * UnicodeModifier
, NumPut("UShort", 10, A)
, A += UnicodeModifier
}
NumPut("UShort", 0, A)
VarSetStrCapacity(&out, -1)
return SubStr(out,1, -1)
}
/**
* Wrap the string so each line is never more than a specified length.
* Unlike LineWrap(), this method takes into account words separated by a space.
* input: "Apples are a round fruit, usually red.".WordWrap(20, "---")
* output: "Apples are a round
* ---fruit, usually
* ---red."
* @param column Specify a maximum length per line
* @param indentChar Choose a character to indent the following lines with
* @returns {String}
*/
static WordWrap(column:=56, indentChar:="") {
if !IsInteger(column)
throw TypeError("WordWrap: argument 'column' must be an integer", -1)
out := ""
indentLength := StrLen(indentChar)
Loop parse, this, "`n", "`r" {
if (StrLen(A_LoopField) > column) {
pos := 1
Loop parse, A_LoopField, " "
if (pos + (LoopLength := StrLen(A_LoopField)) <= column)
out .= (A_Index = 1 ? "" : " ") A_LoopField
, pos += LoopLength + 1
else
pos := LoopLength + 1 + indentLength
, out .= "`n" indentChar A_LoopField
out .= "`n"
} else
out .= A_LoopField "`n"
}
return SubStr(out, 1, -1)
}
/**
* Insert a line of text at the specified line number.
* The line you specify is pushed down 1 and your text is inserted at its
* position. A "line" can be determined by the delimiter parameter. Not
* necessarily just a `r or `n. But perhaps you want a | as your "line".
* input: "aaa|ccc|ddd".InsertLine("bbb", 2, "|")
* output: "aaa|bbb|ccc|ddd"
* @param insert Text you want to insert.
* @param line What line number to insert at. Use a 0 or negative to start inserting from the end.
* @param delim The character which defines a "line".
* @param exclude The text you want to ignore when defining a line.
* @returns {String}
*/
static InsertLine(insert, line, delim:="`n", exclude:="`r") {
if StrLen(delim) != 1
throw ValueError("InsertLine: Delimiter can only be a single character", -1)
into := this, new := ""
count := into.Count(delim)+1
; Create any lines that don't exist yet, if the Line is less than the total line count.
if (line<0 && Abs(line)>count) {
Loop Abs(line)-count
into := delim into
line:=1
}
if (line == 0)
line:=Count+1
if (line<0)
line:=count+line+1
; Create any lines that don't exist yet. Otherwise the Insert doesn't work.
if (count<line)
Loop line-count
into.=delim
Loop parse, into, delim, exclude
new.=((a_index==line) ? insert . delim . A_LoopField . delim : A_LoopField . delim)
return SubStr(new, 1, -(line > count ? 2 : 1))
}
/**
* Delete a line of text at the specified line number.
* The line you specify is deleted and all lines below it are shifted up.
* A "line" can be determined by the delimiter parameter. Not necessarily
* just a `r or `n. But perhaps you want a | as your "line".
* input: "aaa|bbb|777|ccc".DeleteLine(3, "|")
* output: "aaa|bbb|ccc"
* @param string Text you want to delete the line from.
* @param line What line to delete. You may use -1 for the last line and a negative an offset from the last. -2 would be the second to the last.
* @param delim The character which defines a "line".
* @param exclude The text you want to ignore when defining a line.
* @returns {String}
*/
static DeleteLine(line, delim:="`n", exclude:="`r") {
if StrLen(delim) != 1
throw ValueError("DeleteLine: Delimiter can only be a single character", -1)
new := ""
; checks to see if we are trying to delete a non-existing line.
count:=this.Count(delim)+1
if (abs(line)>Count)
throw ValueError("DeleteLine: the line number cannot be greater than the number of lines", -1)
if (line<0)
line:=count+line+1
else if (line=0)
throw ValueError("DeleteLine: line number cannot be 0", -1)
Loop parse, this, delim, exclude {
if (a_index==line) {
Continue
} else
(new .= A_LoopField . delim)
}
return SubStr(new,1,-1)
}
/**
* Read the content of the specified line in a string. A "line" can be
* determined by the delimiter parameter. Not necessarily just a `r or `n.
* But perhaps you want a | as your "line".
* input: "aaa|bbb|ccc|ddd|eee|fff".ReadLine(4, "|")
* output: "ddd"
* @param line What line to read*. "L" = The last line. "R" = A random line. Otherwise specify a number to get that line. You may specify a negative number to get the line starting from the end. -1 is the same as "L", the last. -2 would be the second to the last, and so on.
* @param delim The character which defines a "line".
* @param exclude The text you want to ignore when defining a line.
* @returns {String}
*/
static ReadLine(line, delim:="`n", exclude:="`r") {
out := "", count:=this.Count(delim)+1
if (line="R")
line := Random(1, count)
else if (line="L")
line := count
else if abs(line)>Count
throw ValueError("ReadLine: the line number cannot be greater than the number of lines", -1)
else if (line<0)
line:=count+line+1
else if (line=0)
throw ValueError("ReadLine: line number cannot be 0", -1)
Loop parse, this, delim, exclude {
if A_Index = line
return A_LoopField
}
throw Error("ReadLine: something went wrong, the line was not found", -1)
}
/**
* Replace all consecutive occurrences of 'delim' with only one occurrence.
* input: "aaa|bbb|||ccc||ddd".RemoveDuplicates("|")
* output: "aaa|bbb|ccc|ddd"
* @param delim *String*
*/
static RemoveDuplicates(delim:="`n") => RegExReplace(this, "(\Q" delim "\E)+", "$1")
/**
* Checks whether the string contains any of the needles provided.
* input: "aaa|bbb|ccc|ddd".Contains("eee", "aaa")
* output: 1 (although the string doesn't contain "eee", it DOES contain "aaa")
* @param needles
* @returns {Boolean}
*/
static Contains(needles*) {
for needle in needles
if InStr(this, needle)
return 1
return 0
}
/**
* Centers a block of text to the longest item in the string.
* example: "aaa`na`naaaaaaaa".Center()
* output: "aaa
* a
* aaaaaaaa"
* @param text The text you would like to center.
* @param fill A single character to use as the padding to center text.
* @param symFill 0: Just fill in the left half. 1: Fill in both sides.
* @param delim The character which defines a "line".
* @param exclude The text you want to ignore when defining a line.
* @param width Can be specified to add extra padding to the sides
* @returns {String}
*/
static Center(fill:=" ", symFill:=0, delim:="`n", exclude:="`r", width?) {
fill:=SubStr(fill,1,1), longest := 0, new := ""
Loop parse, this, delim, exclude
if (StrLen(A_LoopField)>longest)
longest := StrLen(A_LoopField)
if IsSet(width)
longest := Max(longest, width)
Loop parse this, delim, exclude
{
filled:="", len := StrLen(A_LoopField)
Loop (longest-len)//2
filled.=fill
new .= filled A_LoopField ((symFill=1) ? filled (2*StrLen(filled)+len = longest ? "" : fill) : "") "`n"
}
return RTrim(new,"`r`n")
}
/**
* Align a block of text to the right side.
* input: "aaa`na`naaaaaaaa".Right()
* output: " aaa
* a
* aaaaaaaa"
* @param fill A single character to use as to push the text to the right.
* @param delim The character which defines a "line".
* @param exclude The text you want to ignore when defining a line.
* @returns {String}
*/
static Right(fill:=" ", delim:="`n", exclude:="`r") {
fill:=SubStr(fill,1,1), longest := 0, new := ""
Loop parse, this, delim, exclude
if (StrLen(A_LoopField)>longest)
longest:=StrLen(A_LoopField)
Loop parse, this, delim, exclude {
filled:=""
Loop Abs(longest-StrLen(A_LoopField))
filled.=fill
new.= filled A_LoopField "`n"
}
return RTrim(new,"`r`n")
}
/**
* Join a list of strings together to form a string separated by delimiter this was called with.
* input: "|".Concat("111", "222", "333", "abc")
* output: "111|222|333|abc"
* @param words A list of strings separated by a comma.
* @returns {String}
*/
static Concat(words*) {
delim := this, s := ""
for v in words
s .= v . delim
return SubStr(s,1,-StrLen(this))
}
}
A compilation of useful array methods.
Examples:
Code: Select all
#include ..\Lib\Misc.ahk
#include ..\Lib\Array.ahk
Print([4,3,5,1,2].Sort(), MsgBox)
Print(["a", 2, 1.2, 1.22, 1.20].Sort("C")) ; Default is numeric sort, so specify case-sensitivity if the array contains strings.
myImmovables:=[]
myImmovables.push({town: "New York", size: "60", price: 400000, balcony: 1})
myImmovables.push({town: "Berlin", size: "45", price: 230000, balcony: 1})
myImmovables.push({town: "Moscow", size: "80", price: 350000, balcony: 0})
myImmovables.push({town: "Tokyo", size: "90", price: 600000, balcony: 2})
myImmovables.push({town: "Palma de Mallorca", size: "250", price: 1100000, balcony: 3})
Print(myImmovables.Sort("N R", "size")) ; Sort by the key 'size', treating the values as numbers, and sorting in reverse
Print(["dog", "cat", "mouse"].Map((v) => "a " v))
partial := [1,2,3,4,5].Map((a,b) => a+b)
Print("Array: [1,2,3,4,5]`n`nAdd 3 to first element: " partial[1](3) "`nSubtract 1 from third element: " partial[3](-1))
Print("Filter integer values from [1,'two','three',4,5]: " ([1,'two','three',4,5].Filter(IsInteger).Join()))
Print("Sum of elements in [1,2,3,4,5]: " ([1,2,3,4,5].Reduce((a,b) => (a+b))))
Print("First value in [1,2,3,4,5] that is an even number: " ([1,2,3,4,5].Find((v) => (Mod(v,2) == 0))))
Code: Select all
/*
Name: Array.ahk
Version 0.3 (24.03.23)
Created: 27.08.22
Author: Descolada
Description:
A compilation of useful array methods.
Array.Slice(start:=1, end:=0, step:=1) => Returns a section of the array from 'start' to 'end',
optionally skipping elements with 'step'.
Array.Swap(a, b) => Swaps elements at indexes a and b.
Array.Map(func, arrays*) => Applies a function to each element in the array.
Array.ForEach(func) => Calls a function for each element in the array.
Array.Filter(func) => Keeps only values that satisfy the provided function
Array.Reduce(func, initialValue?) => Applies a function cumulatively to all the values in
the array, with an optional initial value.
Array.IndexOf(value, start:=1) => Finds a value in the array and returns its index.
Array.Find(func, &match?, start:=1) => Finds a value satisfying the provided function and returns the index.
match will be set to the found value.
Array.Reverse() => Reverses the array.
Array.Count(value) => Counts the number of occurrences of a value.
Array.Sort(OptionsOrCallback?, Key?) => Sorts an array, optionally by object values.
Array.Shuffle() => Randomizes the array.
Array.Join(delim:=",") => Joins all the elements to a string using the provided delimiter.
Array.Flat() => Turns a nested array into a one-level array.
Array.Extend(arr) => Adds the contents of another array to the end of this one.
*/
Array.Prototype.base := Array2
class Array2 {
/**
* Returns a section of the array from 'start' to 'end', optionally skipping elements with 'step'.
* Modifies the original array.
* @param start Optional: index to start from. Default is 1.
* @param end Optional: index to end at. Can be negative. Default is 0 (includes the last element).
* @param step Optional: an integer specifying the incrementation. Default is 1.
* @returns {Array}
*/
static Slice(start:=1, end:=0, step:=1) {
len := this.Length, i := start < 1 ? len + start : start, j := Min(end < 1 ? len + end : end, len), r := [], reverse := False
if len = 0
return []
if i < 1
i := 1
if step = 0
Throw Error("Slice: step cannot be 0",-1)
else if step < 0 {
while i >= j {
r.Push(this[i])
i += step
}
} else {
while i <= j {
r.Push(this[i])
i += step
}
}
return this := r
}
/**
* Swaps elements at indexes a and b
* @param a First elements index to swap
* @param b Second elements index to swap
* @returns {Array}
*/
static Swap(a, b) {
temp := this[b]
this[b] := this[a]
this[a] := temp
return this
}
/**
* Applies a function to each element in the array (mutates the array).
* @param func The mapping function that accepts one argument.
* @param arrays Additional arrays to be accepted in the mapping function
* @returns {Array}
*/
static Map(func, arrays*) {
if !HasMethod(func)
throw ValueError("Map: func must be a function", -1)
for i, v in this {
bf := func.Bind(v?)
for _, vv in arrays
bf := bf.Bind(vv.Has(i) ? vv[i] : unset)
try bf := bf()
this[i] := bf
}
return this
}
/**
* Applies a function to each element in the array.
* @param func The callback function with arguments Callback(value[, index, array]).
* @returns {Array}
*/
static ForEach(func) {
if !HasMethod(func)
throw ValueError("ForEach: func must be a function", -1)
for i, v in this
func(v, i, this)
return this
}
/**
* Keeps only values that satisfy the provided function
* @param func The filter function that accepts one argument.
* @returns {Array}
*/
static Filter(func) {
if !HasMethod(func)
throw ValueError("Filter: func must be a function", -1)
r := []
for v in this
if func(v)
r.Push(v)
return this := r
}
/**
* Applies a function cumulatively to all the values in the array, with an optional initial value.
* @param func The function that accepts two arguments and returns one value
* @param initialValue Optional: the starting value. If omitted, the first value in the array is used.
* @returns {func return type}
* @example
* [1,2,3,4,5].Reduce((a,b) => (a+b)) ; returns 15 (the sum of all the numbers)
*/
static Reduce(func, initialValue?) {
if !HasMethod(func)
throw ValueError("Reduce: func must be a function", -1)
len := this.Length + 1
if len = 1
return initialValue ?? ""
if IsSet(initialValue)
out := initialValue, i := 0
else
out := this[1], i := 1
while ++i < len {
out := func(out, this[i])
}
return out
}
/**
* Finds a value in the array and returns its index.
* @param value The value to search for.
* @param start Optional: the index to start the search from. Default is 1.
*/
static IndexOf(value, start:=1) {
if !IsInteger(start)
throw ValueError("IndexOf: start value must be an integer")
for i, v in this {
if i < start
continue
if v == value
return i
}
return 0
}
/**
* Finds a value satisfying the provided function and returns its index.
* @param func The condition function that accepts one argument.
* @param match Optional: is set to the found value
* @param start Optional: the index to start the search from. Default is 1.
* @example
* [1,2,3,4,5].Find((v) => (Mod(v,2) == 0)) ; returns 2
*/
static Find(func, &match?, start:=1) {
if !HasMethod(func)
throw ValueError("Find: func must be a function", -1)
for i, v in this {
if i < start
continue
if func(v) {
match := v
return i
}
}
return 0
}
/**
* Reverses the array.
* @example
* [1,2,3].Reverse() ; returns [3,2,1]
*/
static Reverse() {
len := this.Length + 1, max := (len // 2), i := 0
while ++i <= max
this.Swap(i, len - i)
return this
}
/**
* Counts the number of occurrences of a value
* @param value The value to count. Can also be a function.
*/
static Count(value) {
count := 0
if HasMethod(value) {
for v in this
if value(v?)
count++
} else
for v in this
if v == value
count++
return count
}
/**
* Sorts an array, optionally by object keys
* @param OptionsOrCallback Optional: either a callback function, or one of the following:
*
* N => array is considered to consist of only numeric values. This is the default option.
* C, C1 or COn => case-sensitive sort of strings
* C0 or COff => case-insensitive sort of strings
*
* The callback function should accept two parameters elem1 and elem2 and return an integer:
* Return integer < 0 if elem1 less than elem2
* Return 0 is elem1 is equal to elem2
* Return > 0 if elem1 greater than elem2
* @param Key Optional: Omit it if you want to sort a array of primitive values (strings, numbers etc).
* If you have an array of objects, specify here the key by which contents the object will be sorted.
* @returns {Array}
*/
static Sort(optionsOrCallback:="N", key?) {
static sizeofFieldType := A_PtrSize * 2
if HasMethod(optionsOrCallback)
pCallback := CallbackCreate(CustomCompare.Bind(optionsOrCallback), "F", 2), optionsOrCallback := ""
else {
if InStr(optionsOrCallback, "N")
pCallback := CallbackCreate(IsSet(key) ? NumericCompareKey.Bind(key) : NumericCompare, "F", 2)
if RegExMatch(optionsOrCallback, "i)C(?!0)|C1|COn")
pCallback := CallbackCreate(IsSet(key) ? StringCompareKey.Bind(key,,True) : StringCompare.Bind(,,True), "F", 2)
if RegExMatch(optionsOrCallback, "i)C0|COff")
pCallback := CallbackCreate(IsSet(key) ? StringCompareKey.Bind(key) : StringCompare, "F", 2)
if InStr(optionsOrCallback, "Random")
pCallback := CallbackCreate(RandomCompare, "F", 2)
if !IsSet(pCallback)
throw ValueError("No valid options provided!", -1)
}
mFields := NumGet(ObjPtr(this) + (4 * A_PtrSize), "Ptr") ; 0 is VTable. 2 is mBase, 4 is FlatVector, 5 is mLength and 6 is mCapacity
DllCall("msvcrt.dll\qsort", "Ptr", mFields, "Int", this.Length, "Int", sizeofFieldType, "Ptr", pCallback)
CallbackFree(pCallback)
if RegExMatch(optionsOrCallback, "i)R(?!a)")
this.Reverse()
if InStr(optionsOrCallback, "U")
this := this.Unique()
return this
CustomCompare(compareFunc, pFieldType1, pFieldType2) => (ValueFromFieldType(pFieldType1, &fieldValue1), ValueFromFieldType(pFieldType2, &fieldValue2), compareFunc(fieldValue1, fieldValue2))
NumericCompare(pFieldType1, pFieldType2) => (ValueFromFieldType(pFieldType1, &fieldValue1), ValueFromFieldType(pFieldType2, &fieldValue2), fieldValue1 - fieldValue2)
NumericCompareKey(key, pFieldType1, pFieldType2) => (ValueFromFieldType(pFieldType1, &fieldValue1), ValueFromFieldType(pFieldType2, &fieldValue2), fieldValue1.%key% - fieldValue2.%key%)
StringCompare(pFieldType1, pFieldType2, casesense := False) => (ValueFromFieldType(pFieldType1, &fieldValue1), ValueFromFieldType(pFieldType2, &fieldValue2), StrCompare(fieldValue1 "", fieldValue2 "", casesense))
StringCompareKey(key, pFieldType1, pFieldType2, casesense := False) => (ValueFromFieldType(pFieldType1, &fieldValue1), ValueFromFieldType(pFieldType2, &fieldValue2), StrCompare(fieldValue1.%key% "", fieldValue2.%key% "", casesense))
RandomCompare(pFieldType1, pFieldType2) => (Random(0, 1) ? 1 : -1)
ValueFromFieldType(pFieldType, &fieldValue?) {
static SYM_STRING := 0, PURE_INTEGER := 1, PURE_FLOAT := 2, SYM_MISSING := 3, SYM_OBJECT := 5
switch SymbolType := NumGet(pFieldType + A_PtrSize, "Int") {
case PURE_INTEGER: fieldValue := NumGet(pFieldType, "Int64")
case PURE_FLOAT: fieldValue := NumGet(pFieldType, "Double")
case SYM_STRING: fieldValue := StrGet(NumGet(pFieldType, "Ptr")+2*A_PtrSize)
case SYM_OBJECT: fieldValue := ObjFromPtrAddRef(NumGet(pFieldType, "Ptr"))
case SYM_MISSING: return
}
}
}
/**
* Randomizes the array. Slightly faster than Array.Sort(,"Random N")
* @returns {Array}
*/
static Shuffle() {
len := this.Length
Loop len-1
this.Swap(A_index, Random(A_index, len))
return this
}
/**
*
*/
static Unique() {
unique := Map()
for v in this
unique[v] := 1
return [unique*]
}
/**
* Joins all the elements to a string using the provided delimiter.
* @param delim Optional: the delimiter to use. Default is comma.
* @returns {String}
*/
static Join(delim:=",") {
result := ""
for v in this
result .= v delim
return (len := StrLen(delim)) ? SubStr(result, 1, -len) : result
}
/**
* Turns a nested array into a one-level array
* @returns {Array}
* @example
* [1,[2,[3]]].Flat() ; returns [1,2,3]
*/
static Flat() {
r := []
for v in this {
if Type(v) = "Array"
r.Extend(v.Flat())
else
r.Push(v)
}
return this := r
}
/**
* Adds the contents of another array to the end of this one.
* @param arr The array that is used to extend this one.
* @returns {Array}
*/
static Extend(arr) {
if !HasMethod(arr, "__Enum")
throw ValueError("Extend: arr must be an iterable")
for v in arr
this.Push(v)
return this
}
}
Some useful functions that can separately be copied into your scripts.
Examples:
Code: Select all
#include ..\Lib\Misc.ahk
; ----------------- Print ----------------------
Print("Hello") ; Uses OutputDebug to print the string 'Hello'
Print({example:"Object", key:"value"},MsgBox) ; Next calls of Print will use MsgBox to display the value
; ----------------- Range ----------------------
; Loop forwards, equivalent to Loop 10
result := ""
for v in Range(10)
result .= v "`n"
Print(result)
; Loop backwards, equivalent to Range(1,10,-1)
Print(Range(10,1).ToArray())
; Loop forwards, step 2
Print(Range(-10,10,2).ToArray())
; Nested looping
result := ""
for v in Range(3)
for k in Range(5,1)
result .= v " " k "`n"
Print(result)
; ----------------- RegExMatchAll ----------------------
result := ""
matches := RegExMatchAll("a,bb,ccc", "\w+")
for i, match in matches
result .= "Match " i ": " match[] "`n"
Print(result)
Code: Select all
/*
Name: Misc.ahk
Version 0.2 (15.10.22)
Created: 26.08.22
Author: Descolada (https://www.autohotkey.com/boards/viewtopic.php?f=83&t=107759)
Credit: Coco
Range(stop) => Returns an iterable to count from 1..stop
Range(start, stop [, step]) => Returns an iterable to count from start to stop with step
Swap(&a, &b) => Swaps the values of a and b
Print(value?, func?, newline?) => Prints the formatted value of a variable (number, string, array, map, object)
RegExMatchAll(haystack, needleRegEx [, startingPosition := 1])
Returns all RegExMatch results (RegExMatchInfo objects) for needleRegEx in haystack
in an array: [RegExMatchInfo1, RegExMatchInfo2, ...]
Highlight(x?, y?, w?, h?, showTime:=0, color:="Red", d:=2)
Highlights an area with a colorful border.
MouseTip(x?, y?, color1:="red", color2:="blue", d:=4)
Flashes a colorful highlight at a point for 2 seconds.
WindowFromPoint(X, Y) => Returns the window ID at screen coordinates X and Y.
ConvertWinPos(X, Y, &outX, &outY, relativeFrom:=A_CoordModeMouse, relativeTo:="screen", winTitle?, winText?, excludeTitle?, excludeText?)
Converts coordinates between screen, window and client.
*/
/**
* Returns a sequence of numbers, starting from 1 by default,
* and increments by step 1 (by default),
* and stops at a specified end number.
* Can be converted to an array with the method ToArray()
* @param start The number to start with, or if 'end' is omitted then the number to end with
* @param end The number to end with
* @param step Optional: a number specifying the incrementation. Default is 1.
* @returns {Iterable}
* @example
* for v in Range(5)
* Print(v) ; Outputs "1 2 3 4 5"
*/
class Range {
__New(start, end?, step:=1) {
if !step
throw TypeError("Invalid 'step' parameter")
if !IsSet(end)
end := start, start := 1
if (end < start) && (step > 0)
step := -step
this.start := start, this.end := end, this.step := step
}
__Enum(varCount) {
start := this.start - this.step, end := this.end, step := this.step, counter := 0
EnumElements(&element) {
start := start + step
if ((step > 0) && (start > end)) || ((step < 0) && (start < end))
return false
element := start
return true
}
EnumIndexAndElements(&index, &element) {
start := start + step
if ((step > 0) && (start > end)) || ((step < 0) && (start < end))
return false
index := ++counter
element := start
return true
}
return (varCount = 1) ? EnumElements : EnumIndexAndElements
}
/**
* Converts the iterable into an array.
* @returns {Array}
* @example
* Range(3).ToArray() ; returns [1,2,3]
*/
ToArray() {
r := []
for v in this
r.Push(v)
return r
}
}
/**
* Swaps the values of two variables
* @param a First variable
* @param b Second variable
*/
Swap(&a, &b) {
temp := a
a := b
b := temp
}
/**
* Prints the formatted value of a variable (number, string, object).
* Leaving all parameters empty will return the current function and newline in an Array: [func, newline]
* @param value Optional: the variable to print.
* If omitted then new settings (output function and newline) will be set.
* If value is an object/class that has a ToString() method, then the result of that will be printed.
* @param func Optional: the print function to use. Default is OutputDebug.
* Not providing a function will cause the Print output to simply be returned as a string.
* @param newline Optional: the newline character to use (applied to the end of the value).
* Default is newline (`n).
*/
Print(value?, func?, newline?) {
static p := OutputDebug, nl := "`n"
if IsSet(func)
p := func
if IsSet(newline)
nl := newline
if IsSet(value) {
val := IsObject(value) ? ToString(value) nl : value nl
return HasMethod(p) ? p(val) : val
}
return [p, nl]
}
/**
* Converts a value (number, array, object) to a string.
* Leaving all parameters empty will return the current function and newline in an Array: [func, newline]
* @param value Optional: the value to convert.
* @returns {String}
*/
ToString(val?) {
if !IsSet(val)
return "unset"
valType := Type(val)
switch valType, 0 {
case "String":
return "'" val "'"
case "Integer", "Float":
return val
default:
self := "", iter := "", out := ""
try self := ToString(val.ToString()) ; if the object has ToString available, print it
if valType != "Array" { ; enumerate object with key and value pair, except for array
try {
enum := val.__Enum(2)
while (enum.Call(&val1, &val2))
iter .= ToString(val1) ":" ToString(val2?) ", "
}
}
if !IsSet(enum) { ; if enumerating with key and value failed, try again with only value
try {
enum := val.__Enum(1)
while (enum.Call(&enumVal))
iter .= ToString(enumVal?) ", "
}
}
if !IsSet(enum) && (valType = "Object") && !self { ; if everything failed, enumerate Object props
for k, v in val.OwnProps()
iter .= SubStr(ToString(k), 2, -1) ":" ToString(v?) ", "
}
iter := SubStr(iter, 1, StrLen(iter)-2)
if !self && !iter && !((valType = "Array" && val.Length = 0) || (valType = "Map" && val.Count = 0) || (valType = "Object" && ObjOwnPropCount(val) = 0))
return valType ; if no additional info is available, only print out the type
else if self && iter
out .= "value:" self ", iter:[" iter "]"
else
out .= self iter
return (valType = "Object") ? "{" out "}" : (valType = "Array") ? "[" out "]" : valType "(" out ")"
}
}
/**
* Returns all RegExMatch results in an array: [RegExMatchInfo1, RegExMatchInfo2, ...]
* @param haystack The string whose content is searched.
* @param needleRegEx The RegEx pattern to search for.
* @param startingPosition If StartingPos is omitted, it defaults to 1 (the beginning of haystack).
* @returns {Array}
*/
RegExMatchAll(haystack, needleRegEx, startingPosition := 1) {
out := []
While startingPosition := RegExMatch(haystack, needleRegEx, &outputVar, startingPosition) {
out.Push(outputVar), startingPosition += outputVar[0] ? StrLen(outputVar[0]) : 1
}
return out
}
/**
* Highlights an area with a colorful border.
* @param x Screen X-coordinate of the top left corner of the highlight
* @param y Screen Y-coordinate of the top left corner of the highlight
* @param w Width of the highlight
* @param h Height of the highlight
* @param showTime Can be one of the following:
* 0 - removes the highlighting
* Positive integer (eg 2000) - will highlight and pause for the specified amount of time in ms
* Negative integer - will highlight for the specified amount of time in ms, but script execution will continue
* @param color The color of the highlighting. Default is red.
* @param d The border thickness of the highlighting in pixels. Default is 2.
*/
Highlight(x?, y?, w?, h?, showTime:=0, color:="Red", d:=2) {
static guis := []
for _, r in guis
r.Destroy()
guis := []
if !IsSet(x)
return
Loop 4
guis.Push(Gui("+AlwaysOnTop -Caption +ToolWindow -DPIScale +E0x08000000"))
Loop 4 {
i:=A_Index
, x1:=(i=2 ? x+w : x-d)
, y1:=(i=3 ? y+h : y-d)
, w1:=(i=1 or i=3 ? w+2*d : d)
, h1:=(i=2 or i=4 ? h+2*d : d)
guis[i].BackColor := color
guis[i].Show("NA x" . x1 . " y" . y1 . " w" . w1 . " h" . h1)
}
if showTime > 0 {
Sleep(showTime)
Highlight()
} else if showTime < 0
SetTimer(Highlight, -Abs(showTime))
}
/**
* Flashes a colorful highlight at a point for 2 seconds.
* @param x Screen X-coordinate for the highlight
* Omit x or y to highlight the current cursor position.
* @param y Screen Y-coordinate for the highlight
* @param color1 First color for the highlight. Default is red.
* @param color2 Second color for the highlight. Default is blue.
* @param d The border thickness of the highlighting in pixels. Default is 2.
*/
MouseTip(x?, y?, color1:="red", color2:="blue", d:=4) {
If !(IsSet(x) && IsSet(y))
MouseGetPos(&x, &y)
Loop 2 {
Highlight(x-10, y-10, 20, 20, 500, color1, d)
Highlight(x-10, y-10, 20, 20, 500, color2, d)
}
Highlight()
}
/**
* Returns the window ID at screen coordinates X and Y.
* @param X Screen X-coordinate of the point
* @param Y Screen Y-coordinate of the point
*/
WindowFromPoint(X, Y) { ; by SKAN and Linear Spoon
return DllCall("GetAncestor", "UInt", DllCall("user32.dll\WindowFromPoint", "Int64", Y << 32 | X), "UInt", 2)
}
/**
* Converts coordinates between screen, window and client.
* @param X X-coordinate to convert
* @param Y Y-coordinate to convert
* @param outX Variable where to store the converted X-coordinate
* @param outY Variable where to store the converted Y-coordinate
* @param relativeFrom CoordMode where to convert from. Default is A_CoordModeMouse.
* @param relativeTo CoordMode where to convert to. Default is Screen.
* @param winTitle A window title or other criteria identifying the target window.
* @param winText If present, this parameter must be a substring from a single text element of the target window.
* @param excludeTitle Windows whose titles include this value will not be considered.
* @param excludeText Windows whose text include this value will not be considered.
*/
ConvertWinPos(X, Y, &outX, &outY, relativeFrom:="", relativeTo:="screen", winTitle?, winText?, excludeTitle?, excludeText?) {
relativeFrom := (relativeFrom == "") ? A_CoordModeMouse : relativeFrom
if relativeFrom = relativeTo {
outX := X, outY := Y
return
}
hWnd := WinExist(winTitle?, winText?, excludeTitle?, excludeText?)
switch relativeFrom, 0 {
case "screen", "s":
if relativeTo = "window" || relativeTo = "w" {
DllCall("user32\GetWindowRect", "Int", hWnd, "Ptr", RECT := Buffer(16))
outX := X-NumGet(RECT, 0, "Int"), outY := Y-NumGet(RECT, 4, "Int")
} else {
; screen to client
pt := Buffer(8), NumPut("int",X,pt), NumPut("int",Y,pt,4)
DllCall("ScreenToClient", "Int", hWnd, "Ptr", pt)
outX := NumGet(pt,0,"int"), outY := NumGet(pt,4,"int")
}
case "window", "w":
; window to screen
WinGetPos(&outX, &outY,,,hWnd)
outX += X, outY += Y
if relativeTo = "client" || relativeTo = "c" {
; screen to client
pt := Buffer(8), NumPut("int",outX,pt), NumPut("int",outY,pt,4)
DllCall("ScreenToClient", "Int", hWnd, "Ptr", pt)
outX := NumGet(pt,0,"int"), outY := NumGet(pt,4,"int")
}
case "client", "c":
; client to screen
pt := Buffer(8), NumPut("int",X,pt), NumPut("int",Y,pt,4)
DllCall("ClientToScreen", "Int", hWnd, "Ptr", pt)
outX := NumGet(pt,0,"int"), outY := NumGet(pt,4,"int")
if relativeTo = "window" || relativeTo = "w" { ; screen to window
DllCall("user32\GetWindowRect", "Int", hWnd, "Ptr", RECT := Buffer(16))
outX -= NumGet(RECT, 0, "Int"), outY -= NumGet(RECT, 4, "Int")
}
}
}
/**
* Gets the position of the caret with UIA, Acc or CaretGetPos.
* Credit: plankoe (https://www.reddit.com/r/AutoHotkey/comments/ysuawq/get_the_caret_location_in_any_program/)
* @param X Value is set to the screen X-coordinate of the caret
* @param Y Value is set to the screen Y-coordinate of the caret
* @param W Value is set to the width of the caret
* @param H Value is set to the height of the caret
*/
GetCaretPos(&X?, &Y?, &W?, &H?) {
; UIA2 caret
static IUIA := ComObject("{e22ad333-b25f-460c-83d0-0581107395c9}", "{34723aff-0c9d-49d0-9896-7ab52df8cd8a}")
try {
ComCall(8, IUIA, "ptr*", &FocusedEl:=0) ; GetFocusedElement
ComCall(16, FocusedEl, "int", 10024, "ptr*", &patternObject:=0), ObjRelease(FocusedEl) ; GetCurrentPattern. TextPatternElement2 = 10024
if patternObject {
ComCall(10, patternObject, "int*", &IsActive:=1, "ptr*", &caretRange:=0), ObjRelease(patternObject) ; GetCaretRange
ComCall(10, caretRange, "ptr*", &boundingRects:=0), ObjRelease(caretRange) ; GetBoundingRectangles
if (Rect := ComValue(0x2005, boundingRects)).MaxIndex() = 3 { ; VT_ARRAY | VT_R8
X:=Round(Rect[0]), Y:=Round(Rect[1]), W:=Round(Rect[2]), H:=Round(Rect[3])
return
}
}
}
; Acc caret
static _ := DllCall("LoadLibrary", "Str","oleacc", "Ptr")
try {
idObject := 0xFFFFFFF8 ; OBJID_CARET
if DllCall("oleacc\AccessibleObjectFromWindow", "ptr", WinExist("A"), "uint",idObject &= 0xFFFFFFFF
, "ptr",-16 + NumPut("int64", idObject == 0xFFFFFFF0 ? 0x46000000000000C0 : 0x719B3800AA000C81, NumPut("int64", idObject == 0xFFFFFFF0 ? 0x0000000000020400 : 0x11CF3C3D618736E0, IID := Buffer(16)))
, "ptr*", oAcc := ComValue(9,0)) = 0 {
x:=Buffer(4), y:=Buffer(4), w:=Buffer(4), h:=Buffer(4)
oAcc.accLocation(ComValue(0x4003, x.ptr, 1), ComValue(0x4003, y.ptr, 1), ComValue(0x4003, w.ptr, 1), ComValue(0x4003, h.ptr, 1), 0)
X:=NumGet(x,0,"int"), Y:=NumGet(y,0,"int"), W:=NumGet(w,0,"int"), H:=NumGet(h,0,"int")
if (X | Y) != 0
return
}
}
; Default caret
savedCaret := A_CoordModeCaret, W := 4, H := 20
CoordMode "Caret", "Screen"
CaretGetPos(&X, &Y)
CoordMode "Caret", savedCaret
}
/**
* Checks whether two rectangles intersect and if they do, then returns an object containing the
* rectangle of the intersection: {l:left, t:top, r:right, b:bottom}
* Note 1: Overlapping area must be at least 1 unit.
* Note 2: Second rectangle starting at the edge of the first doesn't count as intersecting:
* {l:100, t:100, r:200, b:200} does not intersect {l:200, t:100, 400, 400}
* @param l1 x-coordinate of the upper-left corner of the first rectangle
* @param t1 y-coordinate of the upper-left corner of the first rectangle
* @param r1 x-coordinate of the lower-right corner of the first rectangle
* @param b1 y-coordinate of the lower-right corner of the first rectangle
* @param l2 x-coordinate of the upper-left corner of the second rectangle
* @param t2 y-coordinate of the upper-left corner of the second rectangle
* @param r2 x-coordinate of the lower-right corner of the second rectangle
* @param b2 y-coordinate of the lower-right corner of the second rectangle
* @returns {Object}
*/
IntersectRect(l1, t1, r1, b1, l2, t2, r2, b2) {
rect1 := Buffer(16), rect2 := Buffer(16), rectOut := Buffer(16)
NumPut("int", l1, "int", t1, "int", r1, "int", b1, rect1)
NumPut("int", l2, "int", t2, "int", r2, "int", b2, rect2)
if DllCall("user32\IntersectRect", "Ptr", rectOut, "Ptr", rect1, "Ptr", rect2)
return {l:NumGet(rectOut, 0, "Int"), t:NumGet(rectOut, 4, "Int"), r:NumGet(rectOut, 8, "Int"), b:NumGet(rectOut, 12, "Int")}
}
Code: Select all
09.09.22: First post for String.ahk
07.10.22: Added Array.ahk, Misc.ahk
12.10.22: A ton of String.ahk bugfixes. Added RegExMatch and RegExReplace (thanks Axlefublr!)
15.10.22: String.ahk: improved WReverse speed. Added SplitPath() (thanks neogna2!). Array.ahk: combined Find and FindIndex into one. Misc.ahk: added RegExMatchAll.
01.03.23: String.ahk: added __Enum (thanks aliztori!)
24.03.23: String.ahk: added Format(). Bug fixes. Array.ahk: added ForEach(). Improved Sort() (introduces breaking change!). Misc.ahk: added GetCaretPos(), IntersectRect().