AutoHotkey 脚本重构小记
上篇博文我提到了最近我进行了 AutoHotkey 的重构。本篇博文就来介绍一下。
PR
21 号我创建了一个名为「v1_to_v2」的分支,顾名思义是要完成 v1 语法向 v2 语法的转换。并创建了一个名为「Convert convertible scripts to v2」的 Pull Request,这个 PR 最后被更名为「Refactor to v2 & Format codes」,即除重构外,也进行了部分格式化以提高可读性。
这个 PR 最终是有 28 个 commits,仅次于 OCRC 的 30 个 commits。但增加代码 1600+ 行,删减代码 1700+ 行,远胜 OCRC,这也是我完成的最大的一个 PR。
好了不扯淡了,来讲讲 PR 的部分内容。
工具
「工欲善其事,必先利其器」。这是我第二篇博文的介绍。同样的这次转换也使用了一些工具。
首先就是 VSCode,真香。
然后装了两个插件,「AutoHotkey Plus Plus」及「AutoHotkey v2 Language Support」。这两个插件在我改代码、Debug 时起到了不可替代的作用,AutoHotkey 维护者用了都说好!
期间尝试过一个工具 AHK-v2-script-converter,试用了一点,但效果不尽人意,因此大部分还是人工转换的,同时加深我的记忆。
过程
大部分的转换还是比较简单的,就不用说什么了。
MouseHand
第一个谈的应该是「MouseHand」这个脚本,这个脚本虽然并没有被废弃,但由于没有使用过,实质上已经是被废弃了。但在重构过程中,我不仅将代码由 v1 转为 v2,还重写了一遍,现在它的效果应该是胜于一开始的。当然我没进行深入测试,只是把参数改小了一点进行简单测试。
由于手疾,不应该长时间使用鼠标。因此我写了个脚本,一段时间高强度使用鼠标后锁定鼠标,必须休息指定时间后才能继续使用。
1 |
|
原设置是每 30 分钟检查一次,期间左键单击使用鼠标超过 300 次就算高强度使用。强制休息 30 秒,然后可以按 Esc 解除锁定。60 秒后自动解除。
然而我当时不知道 BlockInput
这个命令(其实搜索一下就有了,当时不知道为什么不知道),于是采用了一个笨方法:首先获取最后鼠标位置,然后每秒移动鼠标到该位置。
1 |
|
这是重构与重写后的代码。使用了 BlockInput
,是彻底禁止了鼠标的移动(但仍然可以使用按键,即只禁止鼠标的移动)。
不仅如此,还使用了「胖箭头函数」,减小了代码的体积。
至此,我完成了第一个比较大的重构,心满意足地关闭了。
SlideWindows
然后应该就是「SlideWindows」。这是一个在窗口组循环的脚本。应该是网课期间我企图记电子笔记,为方便多窗口激活而写的一个脚本。很显然这也是一个没使用过的脚本。
1 | Show() { |
这是原脚本。比较粗糙,大体就是 Win + Q 在窗口组循环,Win + W 将当前活跃窗口加入窗口组,Win + E 显示窗口组 id,Win + R 显示列表、输入数字以移除某个窗口。这四个按键也是测试脚本中最常用的了。
重构后遇到了几个问题。首先是显示 id,显示一个 id 有啥用,我咋知道 id 对应哪个窗口,这个显示了跟没显示毫无区别。然后就是,假如窗口组中某个窗口关闭了,那么将要激活这个窗口时 v2 会报错,因为 v2 找不到这个窗口,而 v1 虽然找不到,但它也不说。
于是针对这两点我改进了一下:现在将显示标题而非 id,也许标题很乱也不明所以,但是清晰度确确实实是提升了。还有就是,抛出异常时捕获,并删除掉空项。
不过相当于浪费了一次按键,因为删除后我没有尝试激活下一个。但如果要考虑下一个激活,那还得考虑下一个是否存在。加上我现在没啥动力做这件事,就搁置了吧。
1 | Show() { |
这是重构后的代码。
重构过程中也发现好几处利用默认以空字串返回的方式偷懒的写法,在 v2 里报错,排查了好一会才找出问题所在。
Vark
Vark 也算我一个大项目了,虽然比 OCRC 小得多,但是这是我第一个完全自主创作,包括库都是自己写的项目。OCRC 引用了外部 JSON 库。不过那还能说我用的函数还都是标准库里的呢,所以 OCRC 仍然是我的第一个比较大的项目。
但是呢 Vark 的重构比较中规中矩,我看了下好像没啥可以谈的。除了我移除了一个参数——Vim 路径。因为我已经加入 PATH 了,就没必要了。
比较大的改动应该就是同步了 vimrc 的修改。不过最近又改动了好多,估计 Vark 的 vimrc 还得跟进。
Vark 的重构并没有进行严格的测试,所以会出什么 bug 也说不定。
Vark 目前还是弱了点,已经一年多没更新过了,希望以后有时间能更新一下吧。
OCRC
基本库
OCRC 的重构就累得多了。首先有 GUI 的就它一个(Focus 也有,但这是一个已经废弃的项目了,所以不参与此次重构),其次里面用到了好几个外部函数:UrlDownloadToVar
(我更名为 Request
)一个,UrlEncode
一个,StrPutVar
一个,Gdip
有几个,然后还有 JSON
库有两个 Load
和 Dump
。
JSON
我没有动过,是直接引入的库。仓库在这。很遗憾的是,这个库最后更新时间是 7 年前,虽然说支持 v2-alpha,但显然正式版是用不了了,我也搜到了相关帖子。
然后我找到了 JXON_ahk2,期间还找到了个蛮新的库 jsongo_AHKv2。但我最终选择了 thqby 的 JSON 库,为此我还需要额外引入一个库 Native
。thqby 的库调用了一个 C++ 编写的 ahk-json.dll
,据称能极大提高解析速度。因此重构后的 OCRC 会在初始时在同路径放一个 ahk-json.dll
。Load
和 Dump
分别变成 parse
和 stringify
。
Request
函数呢,我找了一下 v2 版本的,但我不想下个库,毕竟 v1 时我就只是单个函数完成的。没发现能满足我需求的,于是就手动重构了 Request
函数,目前来看没出什么问题。
StrPutVar
是一个用在 UrlEncode
的函数。之前精简代码时我把它第三个参数删了,但可笑的是调用时还是有三个参数,然而没报错,仍然能用。
至于 UrlEncode
,我在论坛找到了一个编码解码的函数,由于我只需要编码,就改了一点。这个函数用不到 StrPutVar
了,就移除了。
Gdip
的几个函数则是找到了 v2 版本的库 AHKv2-Gdip,并根据名字替换了,还把单行的函数移除了,直接替换以精简代码。
Common
库里还有几个自己编写的函数。
ReadIni
是一个读取 ini 文件的函数,v1 时 IniRead
是命令,无法写在表达式。而 v2 中一切命令皆函数,因此这个就弃用了。但在重构过程中我发现这两个函数 Section 和 Key 位置是反的。这个 ReadIni
一开始是从别的 OCR 继承下来的,因此保留了这个。我觉得 ReadIni
这个先 Key 再 Section 填参数的操作很离谱,只是我现在才意识到。
Img2Base
除了把一个单行函数移除了外,没有变动。
GetScreenshot
这个函数就是要获取截图。除了重构外还修改了部分内容(也会讲 PR 后修改的部分内容)。
v1 时在函数内清空剪贴板,而这个操作在 v2 被我移到主函数里了。同时进行了剪贴板内容的保存和恢复。但仔细一想最终结果出来还是要进剪贴板的,这样做好像没啥意义哎。
同时比较离谱的是函数没有引入任何参数,而是直接 global 了四个设置变量。这个操作简直逆天,传个参都不愿意,我写下这行 global 时脑袋是给驴踢了吗?于是在重构时果断拨乱反正。
截图的逻辑大概是这样的:如果开启了外部截图支持,同时提供了外部截图的命令,就调用这个命令,如果未开启、未提供命令或调用失败,就会抛出一个错误,捕获这个错误后使用自带截图操作。
接下来是 PR 后的修改。
一开始自带截图使用 Win + Shift + S 调用截图。然而这有个隐患:要是用户覆盖了这个截图键,那就不行了。为此我在网上冲浪,在 Stack Overflow 查找到了一个方式,使用命令 snippingtool /clip
进行截图。然后我就兴冲冲地提交了。然后我想着在 Windows11 测试一下,结果我惊恐地发现,不行!它会直接打开那个截图工具!然后我就只好继续找,终于在 ElevenForum 找到了通用的解决方案:explorer ms-screenclip:
。这下总没问题了。
然后我通过正则获取外部截图的 exe,以作为 ahk_exe
,检测截图是否完成。我将 SnipPath 更名为 SnipEXE,而且稍微改了一点正则,把文件名禁止的字符都加进去了,可以更精准。
Mathpix 库
接下来是 Mathpix 库的内容。
首先的更改就是不再为 Mathpix 提供为空的默认参数,直接不提供默认参数,Baidu 库也是。因为外部调用都是有传参的,而且默认参数就算了,默认个空字符串算什么。
同时修改了参数名称,微调了设置所在参数位置,使设置更清晰。
Mathpix 类精简得只剩下 __New
和 __Show
两个方法,移除了 __FocusSelect
__Clip
及 GuiEscape
。但并不是说删掉了功能。
__Clip
被一个一行的胖箭头函数 Clip
取代。
Clip(CtrlObj, *) => A_Clipboard := CtrlObj.Value |
并使用 OnEvent
绑定在 Edit 控件,在 Focus 事件时触发。更清晰明了,也更简洁。以更为优雅的方式实现了 Click2Clip 功能。而这个功能在 v1 是通过 OnMessage
实现的。
1 | this.UpdateClip := ObjBindMethod(this, "__Clip") |
首先将 __Clip
绑定到 this,然后将 0x201 事件关联绑定函数对象(不可以直接用 this.__Clip
,后面会讲讲我的推测)。根据Windows 消息列表,0x201 是「按下鼠标左键」的事件,也就是说它监听了全部鼠标左键的消息,然后让我们看看关联到的函数干了什么。
1 | __Clip(wParam, lParam, msg, hwnd) { |
四个参数是必需的,但只用到了一个 Hwnd,是窗口的唯一标识。
之前创建 GUI 时有行命令是 GroupAdd Mathpix, ahk_id %MRW%
,将窗口加入了 Mathpix 组里。现在检测如果不存在这个组,就结束监听并返回。
然后获取当前控件关联变量名,假如在目标四个里就获取内容,保存到剪贴板里。
也许其实有更好的方案只是我当时没发现。但我的这个实现实在是烂透了,极其不优雅。不仅监听了无意义的左键消息,还要靠关联变量名判断是不是在控件上。而靠 OnEvent
实现则清晰,优雅多了。
__FocusSelect
也被一行胖箭头函数 FocusSelect
取代。
FocusSelect(CtrlObj) => (CtrlObj.Focus(), Clip(CtrlObj)) |
而原本的实现也比较烂。
1 | __FocusSelect(control) { |
最后一个 GuiEscape
也是用 OnEvent
实现的,甚至不需要再定义一个函数。
this.__Results[this.id].OnEvent("Escape", (GuiObj) => GuiObj.Destroy()) |
其中 this.__Results[this.id]
就是 GUI 对象,后面也会解释。
原来则是这样,虽然也比较简洁,但肯定不如上面好。
1 | GuiEscape() { |
Baidu 库
然后是 Baidu 库的内容。
加了 Token 函数的结果的判断,防止 Token 获取失败后继续执行,造成两次报错。
同时还修改了一点 __Token
函数的内容,由于 v1 不严谨的机制,v2 报错了才发现不对。
Baidu 中一些函数进行了更名,使其用途更明显和清晰。
跟 Mathpix 类似,Baidu 库也在不影响功能的前提下移除了两个方法:__Update
及 GuiEscape
。以下都以 Format 为例。
1 | Gui %id%:Add, DropDownList, x+5 w90 hwndformathwnd AltSubmit Choose%formatstyle%, 智能段落|合并多行|拆分多行 |
根据我浅薄的知识,使用 g-标签可以使控件在更新时触发函数。然而我的处理函数,例如 __Format
都在类里,直接 g__Format
无法传递 this,因此我就只能先在外部绑定,再把 g-标签通过控件 Hwnd 添加到控件上。这样在 Format 对应控件更新时,结果能及时作出反馈。也许还有更好的办法,但我确实不知道。
当然这样做也确实有点抽象,在 v2 中仍然是使用 OnEvent
解决。
1 | this.__Results[this.id].AddDropDownList("x+5 w90 vFormatStyle AltSubmit Choose" this.config["format_style"], ["智能段落", "合并多行", "拆分多行"]).SetFont("s12") |
this.__Results[this.id]["FormatStyle"]
是 Format 对应控件对象。用 OnEvent
绑定,在 Change 事件,即更改时触发。优雅得多喔。
__Clip
方法保留了,因为处理的方法也在用。但是也变成一个胖箭头函数了。
1 | __Clip(hwnd := "") { |
__Clip(CtrlObj, *) => (this.result := CtrlObj.Value, A_Clipboard := this.result) |
GuiEscape
则是跟 Mathpix 库里一样的处理。
文本处理的方法都没进行变动,一部分原因是我看不懂了。以后有机会还是要处理一下,比如那个「智能空格」会把 OCR 的 URL 弄得一团糟。
主程序
更改了部分变量名及 ini 配置文件的键名,使程序更加清晰。
令人瞠目结舌的是主程序没有(定义)任何一个函数,全是标签。(不过也不至于会去用 Gosub
什么的)
现在命令皆函数就舒服得多了。拿开头的 Menu 作为示范。
1 | Menu Tray, Add, 设置, Setting |
1 | A_TrayMenu.Add("设置", (*) => SettingGUI()) |
然后不知道为啥我要试图以管理员权限启动,这个应该也是从之前继承下来的,现在移除了。
读取 ini 配置我也从 loop 换成了 for,这个 loop 及里面诡异的变量名设置都是继承下来的。现在终于彻底剔除掉这些痕迹了。
发现创建 ini 配置文件用的就是 Gosub
,打脸了。不过我也意识到一个问题:如果打开 OCRC 后删掉配置文件,然后打开随便一个 OCR,就会报错,看来得在 OCR 前额外加个判断。
然后还有两个额外标签(实际上是三个) GETV
GBaidu_Hotkey
及 GMathpix_Hotkey
,后两个是一类的就只放前两个了。这些标签通过控件 g-标签进行关联。与上面提到的类似。
1 | GETV: |
GETV
这个也是继承的好像,应该是 "Get Variable" 的意思。首先获取了当前的 Tab 控件名作为 Section 名,然后写入 ini 对应 Section 的键值。特别地,如果是开关 OCR 则问要不要重启。
插嘴
重看文章找错时发现「是」和「G」左边的引号之间有空格。结果我一看源码发现用的是「"」,看来是之前设置的 typographer 的功效。只不过对我来说好像有点碍事。
其实完全可以多做一个(两个)标签,专门关联开关的两个 CheckBox,不知道我当时为啥没做,宁可重启。
GBaidu_Hotkey
则是专门关联 Hotkey 控件的,同时加了一个 Baidu_HotkeyTemp
作为临时热键,在热键更换时禁用废弃热键(没找到删除热键的方法,论坛上找到的也说无法删除,只能禁用)。
在 v2 里就简洁一点了。同时为 OCR 开关的 CheckBox 单独关联了函数,所以现在已经不需要重启了。
1 | UpdateVar(CtrlObj, *) => IniWrite(OCRC_Configs[CtrlObj.Name] := CtrlObj.Value, OCRC_ConfigFilePath, CtrlObj.Gui["Tabs"].Text, CtrlObj.Name) |
UpdateVar
和 UpdateHotkey
起的分别就是 GETV
和 GBaidu_Hotkey
的作用。然后跟上面一样 OnEvent
关联 Click 或 Change 事件。
踩坑
未初始化变量不可使用
在 v1 中未初始化变量是能直接用的,值视为空,但在 v2 中会报错。这让我一些 v1 中没注意到的犄角旮旯出了问题。
动态变量不再可用
v1 里有个很骚的操作就是动态变量,下面是一个例子。
1 | a := "b" |
这个例子中,会产生一个变量 b
,它的值是字符串 c
。但这在 v2 中不可用,原因其实就是上面那一点,未初始化变量不能直接用。
OCRC 中引入配置文件的内容我用的就是动态变量,OCRC 中结果 GUI 我用的也是名为 id 动态变量。因此为了间接实现动态变量,我创建了 OCRC_Configs
与 this.__Results
的 Map,并将原动态变量的名作为键,原动态变量的值作为值。不过我想,结果 GUI 似乎用不到动态变量,找个机会删掉吧。
FileInstall
我的 OCRC 主程序最上方有两行:
1 | if !FileExist("ahk-json.dll") |
这两行是让编译时将 lib
文件夹下的 ahk-json.dll
打包进 exe,并在使用时如果当前目录不存在,就拉出来。
一开始我用的是 FileInstall("ahk-json.dll", "ahk-json.dll", 1)
,但死活不成功。最后才搜到两个路径不能相同。
话说回来第三个选覆盖好像没啥用,毕竟我都检测了文件不存在。
GuiObj & CtrlObj
即 GUI 和控件对象。
下面是 OCRC 的 Baidu 库里的一段:
1 | this.__Results[this.id] := Gui(, this.id) |
一开始我是这样写的:
1 | this.__Results[this.id] := Gui(, this.id).OnEvent("Escape", (GuiObj) => GuiObj.Destroy()) |
看起来很不错,多好哇,还挺省空间的。
然而这会报错。因为 GUI 和控件对象经过 SetFont
OnEvent
等处理后返回的是个空字符串(或者是别的?我不记得了),并不是对象。
ObjBindMethod
这其实不只是 v2 的坑,甚至说这其实应该不算坑,只是我学艺不精罢了。
this.__Results[this.id]["FormatStyle"].OnEvent("Change", ObjBindMethod(this, "__Format")) |
这个我原本的写法是
this.__Results[this.id]["FormatStyle"].OnEvent("Change", this.__Format) |
然后这是 __Format
1 | __Format(CtrlObj, *) { |
然后它除了刚触发时能用外,其余时候都会报错,大意是整数没有 Value 这个值。我去 Debug 也发现弄下来的 CtrlObj 居然是个整数,真是令人百思不得其解。
于是我就按照 v1 的方法用 ObjBindMethod 去弄就成功了。
然后我无意间发现 OnEvent
传的参数一般有两个,CtrlObj 及 Info,由于 Info 没用,我就用 * 代替了,想传啥传啥,想传几个传几个,然后呢 Info 的类型正好是整数。但这也并没有引起我的关注。
直到昨天深夜里我躺在床上,猛地才想通了:__Format
作为方法,隐式地包含了一个参数 this,而唯一能成功运行的那行代码长下面那样。
this.__Format(this.__Results[this.id]["FormatStyle"]) |
这行代码其实就包含了参数 this。
然而 OnEvent
的函数对象不能包含参数,因此实际上我的 __Format
是要至少两个参数 this 和 CtrlObj,最后传入的是 CtrlObj 与 Info。也就是说,参数错位了。这样也就解释了为何 CtrlObj 是个整数了,因为传入的 Info 本身就是整数。
而 ObjBindMethod 则解决了这个问题。它应该是将 this 与 __Format
绑起来作为一个新的函数对象了。同样的,如果 OnEvent
里的函数对象要传参,也必须用 Bind 而不能加参数,这个我在搜索过程中也发现了相关帖子。
v1 时我也遇到了这个问题。当时也是苦想很久未果,搜索一大片后终于发现了 ObjBindMethod 这个解法,虽然不知其原理,但也就用下去了。
不过现在我大概是知道原理了。希望我理解的没错。
Map
v2 里没有下面的写法了
a := {"m": 1, "n": 2} |
必须这样写
a := Map("m", 1, "n", 2) |
这其实让我百思不得其解。不过 H 版是支持的,H 版还支持 JSON 和 YAML 解析呢。(这里的 H 版指的不是 HotkeyIt 的 AutoHotkey_H,而是 thqby 的合并了 H 版内容的 AutoHotkey_L 的分支)
同时也不可以用 a.m
读取了,必须用 a["m"]
。还是有点不习惯的。
比大小
这个 v1 也是,我只是发现 v2 也有这个现象罢了。看来连续比大小是无法实现的,必须用 &&
。
1 < 3 < 2
的值是 1。原理就是 1 < 3
是 1,1 < 2
也是 1,因此结果是 1。
ThisHotkey
例如在 MouseHand 里有一段是这样的:
1 | ~LButton:: |
可见 KeyLeftButton
及 UnlockMouse
这两个都有 ThisHotkey 这个参数,但都没有显式使用。
我直接引用文档内容:
当热键被触发时, 热键的名称作为其第一个参数传递, 参数名为
ThisHotkey
(不包括尾部的冒号).
同样的例子也出现在 OCRC 中。
1 | OCRC_BaiduOCR(ThisHotkey) { |
GUI 控件
首先是 Text,不设定 h 时文字有时会显示不完整。在 v1 没出现这个问题(但印象中确实遇到过,不过 v1 代码里确实是没有设置 h)。懒得特意去截图了。
然后是 Edit 及 UpDown,这个纯粹是我眼瞎。UpDown 选项中有个「0x80」,v1 的脚本里我是用了这个选项的。它用来省略千位分隔符。然后文档说「然而, 通常不使用此样式, 因为当脚本从 UpDown 控件(而不是其伙伴控件) 获取的数字中不包含分隔符」,我想也是,有个千位分隔符也不碍事,方便看数字(虽然我的数字都不大)。于是就没加这个选项了。
于是在测试时,报错了,说你怎么要数字传个字符串过来啊。我一看配置文件,1,000
。哎你怎么不讲信用呐,不是说不包含分隔符吗?于是我一气之下又加了这个选项。
后面再一看,「从 UpDown 控件(而不是其伙伴控件)」,原来如此,我就是从 Edit 控件获取数字的,误会它了。不过也懒得改了,得改关联内容,好麻烦,算了算了,这千位分隔符不要也罢,反正本来平时也就没用过。
冒号
v1 的冒号要双写转义,比如要让 a
等于字符串 Hello "World"!
,b
等于字符串 ""
,应该这么写:
1 | a := "Hello ""World""!" |
有时候冒号多得我都绕晕了。v2 改为 `"
1 | a := "Hello `"World`"!" |
写个小抄,在内联代码中用 `
需要双写,同时要有空格间隔,即这样写:`` ` ``
。
特点
我累了,写了快六个小时了还没写完。以后有时间再说吧。
延续行
在 v1 时想要延续行得把 ,
放行首。比如这是 v1 的 Start
:
1 | Scripts := [ ;"Basic\Shutdown\Shutdown" |
跟别的语言格格不入。虽然实现了,但是太丑陋了。
而 v2 就可以使用别的语言的延续行方式了,看起来就美观得多了:
1 | Scripts := [ |
命令
删除 "命令语法". 已经没有了 "命令", 只有 函数调用语句, 它们只是没有括号的函数或方法调用.
这真是太好了,随便找几个例子。
比如上面提到的 IniRead
就是一个很好的例子。
RegRead ProxyStatus, HKEY_CURRENT_USER, Software\Microsoft\Windows\CurrentVersion\Internet Settings, ProxyEnable |
这是 Tips/Run
中用来切换系统代理开关的热键中获取代理状态的一行。它的意思是讲注册表的值赋给 ProxyStatus
这个变量。而在 v2 是这样写:
ProxyStatus := RegRead("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings", "ProxyEnable") |
更加清晰明了。
MouseGetPos xcursor, ycursor |
MouseGetPos(&xcursor, &ycursor) |
然后是 Vark
中获取鼠标位置的命令。在 v2 中是传递了变量的引用。
ErrorLevel
在网上看到过将 ErrorLevel 形容为「公共厕所」,让我直接笑出声。因为十分贴切。
在 v1,只要是出错就设置 ErrorLevel,要返回信息就设置 ErrorLevel。这是 v1 中 ErrorLevel 的文档,好多个函数命令都设置 ErrorLevel。
1 | IsChineseMode() { |
这是 v1 中检测是不是中文输入法的代码。SendMessage
设置了 ErrorLevel。
1 | IsChineseMode() { |
而这是 v2。
在 v2 中很多以前设置 ErrorLevel 的现在会抛出错误。虽然处理错误挺麻烦的,但比起弄 ErrorLevel,还是处理错误比较好。
总之这个公共变量 ErrorLevel 这是让人痛恨。写起来爽,但是维护时累的要死。
胖箭头函数
原文是「Fat arrow function」,文档直译为「胖箭头函数」。跟 JavaScript 的箭头函数形式上很像。
这个写法节省了很多空间,上面也有很多例子。不过最好的例子我觉得应该是下面这个。
1 | ^Space:: |
这是 v1 中切换代理的快捷键。切换后它会显示一个 ToolTip 显示当前代理状态。当然这不能一直显示,于是用 SetTimer
设置 1s 后移除。这也是官方文档给的典型范例。
而在 v2 中使用胖箭头函数 + 命令皆函数,可以砍掉一个标签。
1 | ^Space::{ |
赋值
v1 中有两种赋值方法,除了 :=
外还有一个我从没用过的 =
,下面两个是等价的。
1 | a = b |
我本来就不喜第一种方法,因此没用过,全用的是第二种。v2 中移除了第一种赋值方法。
相等
v1 与 v2 都能用 =
判断相等,不过 v2 加入了一个 ==
,可以要求大小写也相同,而 =
是忽略大小写的。重构过程中我将 =
全改为 ==
了。
百分号
v1 中很多要用百分号的地方终于解脱了,这让 AutoHotkey 语法更像通俗的编程语言了。目前我的 v2 代码还没出现用到百分号的场景了。
OTB
v2 完全支持了 OTB,即下面这种写法。
1 | if x < y { |
v1 也是支持的,但是有例外。这是 OCRC
中 __Space
的一段 loop:
1 | loop parse, result, " |
把 {
移到前一行末尾会出错,因为它把分隔符当 "{
了。而 v2 则没事(还用三元表达式简化了代码):
1 | loop parse result, "`"" |
先这样吧。以后想得到再来补充。