光影之中成长

本文初建于我的二十岁生日,旨在对我的前二十年人生进行一个大概的回顾。

在一年之前大概就有规划要写了,当时还有想怎么选题,最终是敲定就写一下我的前二十年,我更想回看的是:当时究竟在学什么、为什么学,也许涉猎稍广但浅尝辄止,以及这些选择最后把我带到了哪里。

当然,说是这前二十年的回顾,实际上更多的是未成年十八年内的回顾,毕竟这两年的内容很多都记录了下来,何况也没有什么太大的变化,变化最剧烈大概还是前十八年。

当然比较早期的历史已经相对久远了,而且我自己也没留下什么材料。这部分主要靠回忆与少量留存材料,时间线难免有局部错位。

与其说是年表,不如说我更想追这样一条线:从游戏开始接触电脑,折腾工具的过程中慢慢搭出方法,最后回头问自己这一切留下了什么。

Scratch & Flash

我和计算机最早的关联已难精确追溯,但最清晰的起点大概是 Scratch 与 Flash。前者现在似乎还在,但后者则已经进棺材了,是个很有年代感的东西了。很多旧网游我想要重温的时候都需要 Flash,之前假期的时候还折腾过用其他方式,重新去看了看些之前沉迷的旧游。

那会似乎小学有电脑课,讲的大概就是这两个?然后回家也会玩一点,也许吧。印象中用 Scratch 做过一些简单的小游戏,例如经典打砖块什么的,也用 Flash 做过一些简单的动画。不过这些东西都没有留存下来。

印象中小学的时候还曾在放学后跟着一位老师学习,学的是啥忘记了,最终是做了一个成品出来。可能是 Flash?我记得我有做过一个运动会的,其中有小人赛跑,还有喝彩音乐素材。小人其实画得比现在的火柴人好,因为起码有跑步姿势。我还记得最开始只会圆圈 + 五笔画的火柴人,然后因为有跑步,于是去网上搜了一下火柴人,因此我就学到了从手脚各多画一笔,画出跑步姿势的方式。毕竟还小,当时没想通怎么画,还记得一开始画得也不成人形,最终才理解,直到现在依旧记忆深刻。

当时对动画的全部理解就是 Flash 了,关键帧、过渡等等。在别的地方写过我脑海中曾有过几个架空的世界,曾经有一个就以文字的形式记录了一小部分,同时附带了一个 Flash 地图。这也是我目前仅存的唯一一个 Flash 文件,但现在已经是打不开了,毕竟没软件了。当时的恢宏构想大概就是用 Flash 做出朝代更迭的那种效果。现在想来真的是「异想天开」,因此似乎只做了开始的版图就放弃掉了。

即使是开始的版图也有一些问题,例如边界是光滑的。我画过很多架空的地图,但都看着并不对劲,不甚满意。直到后面我才意识到问题出在边界,我画的边界都是很光滑的,因此在交界处会非常突兀。后面还看到很多其他人自制的架空地图那更是叹为观止,比我的精致得多。

思来想去还是不放当时的寥寥文字记录了,看着有点羞耻。看元信息,其实不算太早,是 18 年初,不过那会都初中了对 Flash 还有印象吗?我印象中就小学玩过耶。

还记得小学时有个课程,似乎是课外的,也许是校本课程,是一个机器人课,我也参加过,忘了为啥。只是当时的我跟现在的我差不多,属于「旁观者」的身份,完全是介入不进去的。当然我其实也对机器人没多少兴趣,因此基本在坐牢,看其他人大放异彩。

信息源

小学的时候还是用着 360 全家桶,装着 360,浏览器也是 360 浏览器(也许是极速版),自然导航也是 360 网址导航。我当时自然是不知道什么是「牛皮藓」,什么是「篡改主页」的,就觉得这个主页有好多信息哇。

说起来我读的网络小说似乎有一些就是从这里给标题党吸引进去了,我印象中有一本是这样的。不过这一本写得不错,我高二还是高三的时候还重读了一番,因此我原谅它了。

重新打开了导航,感觉有一些变化,一个比较显著的大概是图变多、变大了,同时有一些所谓的短剧等,见证了时代的变迁。

当然,现在我自然是对这玩意避之不及,要求自己的新标签页是干干净净的,需要什么资讯就自己检索,不依赖默认推送。之前觉得也许我后面就都是这样了,但又想了想,我现在有精力去折腾、自己看想要的,但也许等我年老体弱气衰后,需要接收的信息更多了、渠道也更繁杂了,再无精力去像现在这样一个一个看,可能会再度成为这样的导航的受众。

就拿 B 站举例吧,我除了极少数外是不关注 UP 的,但是我看得最多的、能到几乎期期不落的,基本上不在这个「极少数」中。其中一个原因是我不喜欢被轰炸,有小红点一定是要清的。还有一个原因就是我也不喜欢无关信息推送,推荐机制就算了,动态的广告推荐我很不喜欢,因此关注的基本上是无广告的,而比较常看的那些有广告的 UP,反倒是靠更为原始的搜索[1],抑或是在上热榜的时候自然而然地去看。

不过我现在也是不清楚了,我也在反思:这种「手动遍历」投入的时间,是否真的比「关注后接受推送」更高效。更何况我大部分遍历其实是无效遍历,其实它们压根没更新,但我一天要遍历几次。这是其中一个例子,像其他平台,也大差不差。

一个属于自己的信息源,可能是我未来会探索的一个方向。

游戏

上面提到了游戏,那就来讲一讲我的游戏史吧。

游戏不只是消遣,它也是我最早接触电脑、形成操作直觉的入口。

7k7k & 4399

说到游戏自然就离不开大名鼎鼎的 7k7k 与 4399 了,10 后应该没怎么用过吧,毕竟手游、端游已经很繁荣了,还会有多少旧时代的残党在页游呢?当然,我不了解。

很多游戏都是在这里玩的,有时候也会在里面探索好玩的游戏。我印象比较深的部分游戏大概是森林冰火人(这个很经典,但其实我不怎么喜欢玩)、火柴人打羽毛球赛车等。其实还有很多类型的游戏没列出来,比如说可能有餐馆做菜之类的,但没之前那些有比较深印象的,因此就不列代表了。前面的游戏我都找了找附上链接,当然,要 Flash,现在也没办法重温一下。

说起来高中的时候有同学课间玩坦克大战,其实我小时候没玩过,记得后面还试了一下,感觉挺难玩呀。

QQ 空间

除了这里的小游戏外还有 QQ 空间的小游戏。除了经典的 QQ 农场、QQ 牧场外,其实还有如 QQ 餐厅、QQ 超市等,当时也是布置地不亦乐乎。甚至初中,也许还有高中一时兴起还会回来看看。当然,现在已经停服了。

还有一个印象比较深的空间游戏就是 TNT 了,也许这个与 QQ 宠物有关?我第一反应是「Q 宠 TNT」。这个我记得似乎挺好玩的,不过也停服了现在。

QQ 宠物也是一个我感触挺深的游戏了,其实它已经不单单是游戏了。其实我对网上说 Q 宠很容易养死这个没啥记忆点,因为我并没有 Q 宠濒临死亡的记忆。但我似乎印象中养的时间最长、也是最后一个 Q 宠并不是第一个,而是很快领养的第二个。当然这些都无从查证了,因为 Q 宠也已经关服,都结束了。

我记得我给他[2]收集了很多东西,甚至有很多绚烂夺目的宝贝,数量少得可怜,自然也不舍得给他用,有点遗憾没能在最后时刻的时候给他挥霍一番,不过可能本来就没机会,因为我似乎是后面才听说关服的。大多数物件我也记不得了,但其中一个倒是印象深刻,就是一个药品「还魂丹」,似乎可以复活的,我好像还有不少?

会带着他在各处转,会跟他玩游戏,也会让他学习、工作。我记得似乎有多种能力可以培养?我最看重的便是知识了。似乎还有多种学科?我也是让他最投入数学、物理等。其实记得他学得比我还超前,毕竟我是小学玩的,他都学到博士了似乎,想到这还有点搞笑。

陪伴是最长情的告白,突然间就想到这句话了。他最长在桌面陪伴在我身边。有的时候会给他点上几下逗他玩,我记得还有个什么游戏,可以在屏幕中进行操作,他似乎是在最底侧干些什么?他也会做出各种各样的姿势来回应我,现在想来着实是有点感动。后面不知道为什么就没玩了,很多游戏都是这样。没能见最后一面,我好遗憾,我连名字都不记得了。

既然说到空间游戏,又想到小时候玩过一个游戏叫「谁是卧底」,但这个谁是卧底跟大众意义上的谁是卧底并不是同一个含义,而是一个 QQ 空间里的警杀游戏。所以相当长一段时间这个名词的第一候选概念我一直是这个游戏,而非大众玩的那种。当然我也觉得这个比原版的好玩就是了。

那会做过一个 PPT,我感觉算是我 PowerPoint 能力的巅峰了。当时用了很多动画效果,模拟出了一场游戏。放在现在我是肯定懒得搞这么复杂的,PowerPoint 做的基本上都是敷衍作——随便挑个模板然后图、文直接糊上去,动画效果、对齐是想都别想的。当然即便是比我现在的复杂,那也只是多加了点动画效果,也并无什么特别之处。

下面是演示效果的视频,经过压缩与加速。游戏模拟大概在 15s:

不过现在的我看到这个 PPT 的第一感觉可能如下:

确实,这个审美现在真的是不敢恭维,滥用艺术字。但对当时的我来说这样效果确实不错。

再说回到这个游戏,其实真挺好玩的哦,我玩的比较多的大概是丧尸那个,原因也很简单,特殊身份最多嘛,做平民多无聊。

不过要说最耿耿于怀的,大概其实是另一个模式,里面有「防暴警察」与「纵火狂魔」,我上面的 PPT 中也有这俩。其中主要是「纵火狂魔」,我记得这个身份似乎是可以在夜里泼汽油,然后后面可以选择引火全部烧死。你别说,挺爽的,但我记得我似乎没成功过,没能解锁成就。这确实一直让我记着,其他身份我基本上都有光辉时刻——恐怖分子精准定位、狙击瞬狙、绑匪撕票等[3]——哪怕是防暴[4]我都烟雾死过人,但就是这个纵火没有光辉战绩。

甚至后面出了一个新模式,我已经忘了,就记得其中有个角色叫「天煞」,另一个好像是什么牛仔?还是有藤曼什么的[5],这些我都爽玩过耶,没玩过多少次,但也有爽的时刻,而纵火首先难拿,玩这个模式还经常变民很无聊,然后好不容易拿了还给干掉了就更血压升高了(尤其是你已经泼了几个人,打算下一晚就开烧)。

天煞这个名字我有印象,因为这是我第一次见「煞」这个字,最初还不会读,甚至查了后还是踉踉跄跄的,后面才逐步认识。当然其实同期还有一个叫「天煞」的我印象中[6]

哦对了我还记得我还干过一件事情——大约是小学或者初一吧——以这个谁是卧底为灵感「创作」过一个类似的游戏。记得似乎当天家里没人,于是在物业那边写作业。写完后我就开始拿出一本笔记本开始写写画画了,最终形成了我的「大作」——傻逼大作战。

现在回看这个命名,还是有点绷不住。其实我印象中我没怎么用「傻逼」这个词骂过人,甚至高中的时候也不喜欢这个词,但我当时命名就是这样命名的,甚至基础角色就有一个「傻逼」。

故事的背景是在精神病院,说什么傻逼也有人权什么的,有什么暴动,然后正反方对抗(在如此抽象的背景下,有个科学家是反方,因为他是科学狂魔,想要杀掉所有傻逼)。其中有一个我印象很深的角色是「夜猫子」,因为我记得我那时听说了一个「传闻」,大概就是数眼睫毛还是啥,数到多少人就会死,因此我有了灵感、把这个写了进去。

还有很多角色我就不一一介绍了,甚至这本本子还在,可以说是相当难得的过往资料了,很难想象我当时构思与撰写时候的精神状态。当然这只是其中记录的一个游戏,还有另一个游戏,基于另一个我沉迷的游戏,但这个就没什么活。

大学的时候有一次去看了眼,发现虽然改版不少,但数据还在,于是爽开两把。下面是一些记录:

首先是第一把,有点生疏了,不过本来也没啥可玩的:

  • 来光速开一把,现在 17:23,玩把僵尸的。杀手。我头刀,刀了个僵尸。
  • 难绷,队友有点逆天,被查杀了还帮忙。第二晚杀了个绑匪。第二天给带了,这下孤军奋战了。怎么这么菜,我当时跟别人玩没那么菜的。
  • 狙了个医生,刀警察给特特保了。危险。狙击给投了,完蛋。
  • 好了,寄了。给投了。怎么这么牛比。再来一把,光速完结。

然后第二把真给我爽到了:

  • 哟,是特特。但不知道为啥好像没法保。哦好像是没投票那样的明显标记,是有的。
  • 狙击狙了杀手,绑匪杀了警察,还刀了警察,服了。
  • 一开始带票没人理遗言小丑,难绷。
  • 保护了狙击,结果恐怖分子炸死了,是平民。
  • 牛仔杀了个杀手,看早上带票如何了。是狙击。
  • 逆天,牛仔狙了警察。噢,是还有一个警察吗?
  • 早上投了杀手。晚上刀了僵尸。绷,警察带票那个人投我。连着保了几天警察。能赢吗?死这么多还没结束。
  • 再保护了杀手刀杀,爽了。
  • 赢了,还差个牛仔。牛仔不敢打枪,一开始我都忘了特特死了会带死,牛仔是对的啊。
  • 可惜没去截图一下游戏过程。挺爽的这把。不过这把居然四个杀手?上把才三个。
  • 回顾的时候还是好爽,真的是非常难得的好局啊。看统计记录我玩了 1k+ 局,即便是在频繁玩的之前这个也属于是不可多得的好局。
  • 一开始狙击狙了个杀手我乐了,蓝狙好狙,不错的开局,然后结果就是连死两个警察,过山车般急转直下。
  • 然后早上忘了谁一开始带票 6,明明警察遗言叫 7 的。不过后面也有人补 7 了,等一定票后我也投 7 了。但当时忘记去记谁第一个投 7 的了。后面改票的也多了,还是成功把绑匪干掉了。
  • 好久没玩了,特特保护没啥特效,只是对方右下角会有小盾牌,第二次保护才注意到。对了,被撕票的是 9 警察,我首把保护了 8,就在旁边。绷,现在看来居然是恐怖分子。
  • 然后凭借印象,好像是 16 第一个投 7 的,于是光速保护 16,结果狙击也狙了 16,成功保护。不过后面恐怖分子就炸了 16,还好只是平民。只能说好民,我要是这把开了个民我就开摆了。
  • 然后牛仔再狙了杀手,早上再投掉了狙击,那就是我特特的天下了,谁都能保,形势再度转好。当然这时候就定 15 警察了。而且因为上把就 3 个杀手,我也以为现在改版了,明明之前不是 4 + 4 吗?小心护着这「最后一个」警察。
  • 结果牛仔狙了个警察,我这时候都傻了,这不是结束了?然后看还没结束,噢难道只是杀手 3 个警察还是 4 个?说起来 UI 也改了,最上面一栏看不到人数,也可能是给挡住了,无语。
  • 这个 2 警察遗言讲 3 5 排,看到我都傻了,这有点歧义吧。我第一反应是从 3 5 中排一个。当然后面就知道了 3 5 好人排除的意思。不过我觉得其实有一个带票就够了,没必要公布好身份,毕竟万一不小心刀到我了就寄了。
  • 然后晚上杀手刀了僵尸。哦,我还很慌张后面会不会尸变,不过一直没有,最后才发现,这是挂机了还是每个子嗣都寄了?
  • 然后投了杀手后还没结束,才意识到还是 4 + 4,那上一把怎么回事?人没满?
  • 晚上继续保护 15,17 还是不放弃去刀 15 啊,给我护住了。牛仔没开枪。
  • 该说不说我这把操作还是拉满的,唯一的败笔是最后早上说话。牛仔最后早上投票的时候说不敢开枪,虽然拿到特特的时候我想到了会带死牵连,但他说的时候我忘了这茬,闲着没事补了一句「没事有我护着」,结果他的意思就是怕打到特特,是我脑子糊涂了。
  • 当然其实靠 2 遗言,3 5 排,当时就剩 3 5 15 17,那也只能是 17 了,照着打也没问题,只是为了保险不开枪是对的。

手游

说了一些页游,其实我也玩过手游。小的时候我妈妈用的是 iPhone,是 4 还是 5,忘记了,里面就有一些游戏。印象最深的是一个搭楼游戏,点一下会盖一层,然后搭歪了上面的空间会减小,正中中心就是完美。印象比较深,是因为我记得我在医院等待的时候就玩过以解闷。

当然真要说玩得比较多的手游,那大概是「天天酷跑」了。不过我写下它的名字后,似乎又不知道该写些什么,写上面的搭楼游戏让我回忆起来年幼多病在医院冰凉梆硬的椅子上等待的时间,但这个我好像没什么独特的记忆点,就记得玩过不少。前阵子还在 B 站看过天天酷跑的视频,其中一些名字又勾起了我部分回忆。

手机上当年微信小程序「跳一跳」非常火,男女老少都在玩。我记得我唯一一次去香港,似乎在车上就是玩跳一跳。后面又出来很多小程序游戏,大部分似乎都是有各种各样的系统,要养成的,我初高中摆烂的时候往往会沉迷几小时玩个几天,然后就不再光顾了。

记得初中毕业的暑假的时候,还跟朋友玩过一个养人游戏,虚拟培养什么的。不过一样也就是玩了一阵,应该放榜前就没玩了。

补充

也许记错了,后面 QQ 空间翻到了图片,不过时间是 19 年最后一天最后半小时。

童年游戏

说起来,上面的 4399 部分还有一个印象比较深的是国际象棋。小学的时候也许是有啥兴趣课,还是怎么的,反正就是买了一副国际象棋,然后后面似乎又给「诱骗」去校外什么地「培训」。我不是很记得了,似乎是在学校旁一栋高楼的一间屋子里面,具体干啥我也记不清了。当时年纪也比较小,一个人到一个陌生场地挺害怕的。

后面还有一本「教材」,里面是残局还是什么?之后我记得我还在家里的角落中翻出来过破烂的残本,只是再然后高中毕业搬家了,也估计是进了回收站。

记得后面似乎还参加过一个比赛,专门到一个场地,不过我也是没什么实力,似乎光速就寄了。

写到这就想起来数独。小学的时候似乎有一个什么班,我忘了是啥了,可能是课后多抽一节,然后有一些有意思的内容。我印象中红蓝眼睛问题我就是第一次从这里听说的,当时还做噩梦。似乎一些古早的资料,甚至包含 Flash 的相关资料(记得我当时打印下来特别宝贝,生怕它折了),在小学的书包。只是搬家后大概也是丢弃了,有点遗憾。也许我数独一开始就是在这里玩的。

然后高中的时候是不是还办过班级的数独大赛?我不太记得了。但高三的时候就有同学有数独题谱,我有的时候无聊,例如说上课、晚自习,就会拿一张来做。当然我数独水平也比较差,更加深入的技巧,例如看四角什么的我不了解,基本要靠假设排除或者铅笔记录等,没法瞪眼看出来,因此有一些我解不出来。

甚至在大学的时候也玩过。例如说我也提到过在数学课的时候曾经玩了一节课的数独,好玩捏。

回归正题国际象棋。反正就是学了点国际象棋,然后 4399 正好有,于是常常在那里下。我印象中那里是有一点难度的,但我也曾经有玩过试试变多几个后的虐杀时刻(当然,有没有被逼和我就记不清了)。记忆中最深的大概就是生病请假在家的时候,似乎就有玩过这个,当时病怏怏地玩。

博主博主,那我们的中国象棋呢?我对国际象棋感情更深,也是因为我先学的它。不过在初中的时候玩的更多的反倒是中国象棋了。这是因为我记得初一教室外面就有棋盘,还有五子棋,经常就是课间或是放学同学聚在一起玩。

当然我水平比较菜,一般是在旁边看别人玩,似乎玩得比较少。但即便如此,依旧是能从放学看到晚上很晚再回家。

忘了是故意还是无意,也许是兼而有之吧,我的国际象棋棋盘是从中间裂开的——也许是裂了一半给它个痛快了,也可能就是不小心摔坏了。然后我后面就把这个国际象棋棋盘当中国象棋棋盘了,中间裂开来正好可以当楚河汉界。

我还记得我有一个小圆盒子,忘了之前是放啥的,兴许是茶叶,装了我一些纸片宝贝,里面似乎还有几个非常小的骰子。似乎是一些我自制的牌,可能是模仿扑克或是什么,也有一些自创的玩法,自己玩。

这些多是和朋友一起玩,或者自己自娱自乐。还有就是跟家人一起玩的,那就有跳棋和扑克,这两个是在大学的时候偶尔也会一起玩玩的。记得大学某次假期最后一阵子玩了挺多把斗地主,我赢得最多,哈哈哈哈哈。跳棋的玻璃弹珠,倒是跟 JMR 的猫眼有点像,因此我现在看 JMR,对它也有一些额外的偏爱。

家里其实还有围棋,一张很大的纸质棋盘,以及两个棋盒。但我其实一直不会下围棋,或者说其实没下过。但要说我不懂围棋规则的话那还是不至于,我只是很多演化的名词听不懂。但是围棋的棋子倒是挺好玩的。具体玩法我记不清了,隐约记得是在地上,由瓷砖的边界圈划出边界,然后似乎就可以双方彼此来弹、攻击。细节不记得了,但我记得每一步我都心中构想了大国之间的征伐与具体的战斗细节,仿佛它真的存在。

写了这些童年游戏,我逐渐想到有一个非常重要的游戏——起床。这个游戏我从小学一直玩,玩到初中,不过到初三就没怎么玩了。玩的范围也是一个圈子,包括我在内的几人从小学一直玩,学校玩、放学路上玩等等,因此也吸纳了一些人一起玩,不仅有同一个班级的,还有不同班的,甚至上初中后的新班级也拉了新人一起玩。作为「老资历」,算是有点话语权的。

这个游戏我网上查了一下,似乎没找到类似的,但我肯定不是原创。游戏规则说简单其实很简单,多人游戏(两个人就能玩,更多人还可以分队),石头剪刀布,赢的人可以进行一步操作,目标是干掉其他人。初始状态所有人在自己家的床上睡觉,因此第一步往往是「起床」,从而这个游戏被我们称为起床——「来玩起床」。

因此最开始一种比较连续的行动轨迹大概是这样的:起床、到商店、买把狙击枪、瞄准你、biu。如果被瞄准了,往往就是紧张时刻了,可能会有赶紧跑开求稳,也可能会反瞄准来进行决战,当然也出现过此时还没起床但绝地反杀的情况。

但如果只是这样,起床的生命力也就不会如此顽强了。起床的生命力的源头,我想大概是创造力。有了狙击枪,就有跑到地下,让你狙不了的方式。

而另一种比较常见的击杀套路就是掐死:起床、到你床边,掐死你。当然这个就太 bug 了,后面比较常见的是要求掐死你之前要先握住脖子。而这也诞生了一些名场面,例如说两个人反复到对方背后,意图掐死对方,而你可能好不容易握住了脖子,对方就又跑到了你身后。

有时候还会出现这样的情况,对方已经到你床边、握住你脖子了,而你还没起床。因此又开发出来一种版本,将劣势转化为优势——起到你身后,直接起床就在对面身后了。当然这后面就给 ban 了。而为了彻底拒绝被背后偷袭,后面还有躲到床底的情况,或者直接起到床底。

石头剪刀布,尤其是只剩下两个人的时候,有时候会遇到连平,平的次数越多,出手就越快,「呃啊」的声音也就越大,彰显自己的气势。

起床归根结底不是一个追求胜负的游戏,它是一个社交游戏,或者更进一步,「圈子游戏」。因此当你可以直接杀掉对手的时候,并不一定会这样做,可以好好戏弄对手。例如说将对方打晕、绑起来就是常见的操作,你只要一击就能干掉对方,但你打晕他、再绑起来(甚至多绑几次),他需要先醒来再挣脱,基本上是难以逃离虎口的。这时候基本上就是「虐杀」时候,往往会给对方上各种酷刑,给予对方各种各样的折磨,当然也有两人结盟合力折磨第三人的情况。

当然起床并不是只有打打杀杀,我上面说过起床是一个展现创造力的地方。有时候双方也会放下戈矛,建设自己的希望图景,例如说有的可能建立一个自己的帝国,有的可能家财万贯等等,所有的愿望都可以在起床中一步一步搭建起来。

起床极盛时刻,是我在回家路上会跟同学一起玩的,走走停停,甚至停在路边一直玩,甚至到楼下坐在那里继续玩(当然,后面还有吹水,不仅仅是玩起床)。

那起床为什么式微了呢?我想可能跟社交重构有关,上了初中后我的社交圈也在发生重构,尤其是初二的时候逐渐也融入了新的社交圈,尽管初一的时候还是吸纳了新人一起玩起床,但终究是原社交圈的产物,在社交重心迁移后,起床就逐渐从我的日常中衰退了。也许偶尔还是会玩上几把,但终究无法像从前那样频繁了,直到最后一次玩,再到现在已经忘了当时添加的一条条细则。

这真是我最有创造力的时候之一了,现在的我如是感觉。除了这样的社交游戏外,还有一些就是「脑海游戏」,仅存在脑海中的游戏,主要在初中,高中也有,但相对少很多了。因为我小学、初中从家里到学校要差不多二十分钟嘛,同时中午又要回家(虽说初中有一段时间似乎是在食堂吃盒饭的?),这些路程路上没啥事干,那就只好胡思乱想了。

想的主要也是帝国战争吧,有的甚至在有了相当长的历史后会记录下来,例如上面提到过的。当然其实在演化很多后,很多前面的也记不清了,上面的记录其实也是不清不楚的。而且很多辉煌的细节,只有想的当时记得,后面就遗忘了。

还有一个有点印象的,大概是高中想的。高中其实这样时间相对少,因此往往是在重复相同的事情,或者说在不断覆盖前面想过的内容。这个想象大约是基于 Minecraft 的,当然其实远超这个。灵感大概是来源于我看红石、生电特别厉害,心中艳羡,于是脑海想象。

内容就是有四个游戏主播(当然,四个人并不是完全地位对等的。尽管我是上帝视角,但「我」是其中一个,同时也是稍厉害的)占领田字地图四块区域,是自己的「王国」,可以在里面建设。不过在一段时间后边界线才会开放。然后我想的是每个国家构建自己炫酷的科技,来攻守。这些科技是我从 Minecraft 中想象而来,并不可能存在,也许会套一些名词,但归根结底是自己的架空世界。

洛克王国

然后就是我比较沉迷的一些网游了。

第一个大概是《洛克王国》,这个应该是小学的时候玩的了。当时似乎也注册过《赛尔号》之类的,但没怎么玩,主要还是这个。前阵子找寻些 Flash 手段的时候,重新登录过。不仅仅是大学,在高中的时候也有重新登录过,甚至拿过回归礼包。

写到这我突然发现似乎没啥好写的了,似乎只能写上次我重新登录进去的见闻与回忆了。不,不能这样!我重新找了一下当时的方式 CefFlashBrowser 再试试。

看了一下,我是 2013 年底加入的,如今也 12 年了。有 262 只宠物,13w 洛克贝。

这是我的形象,可爱的迪莫。

然后这是我的阵容。之前重登的时候带着这套阵容去哪里打了一下,拼尽全力无法战胜,全灭了,后面补了状态。

可以看到这个主力就是冥暗幽王,不过这个是比较后面获得的,可以看见是 10.1。不过意外的是这个赤炎飞龙是 16 年获得的,但我没啥培养的印象,估计是送的吧。

还有就是可爱的幽兰雪魅,她真的好可爱啊,虽然现在长大形态没小时候可爱,但依旧是好可爱,我当时就是这样认为的。

还有圣水守护。没错啊,当时喵喵、火花、水蓝蓝我选的是水蓝蓝。听说男生选喵喵尤其是火花多一点,女生主要选的是水蓝蓝,但我当时选的就是水蓝蓝。一是我当时最喜欢的颜色是蓝色,应该是因为是天空的颜色[7],二是我觉得水蓝蓝宝宝好可爱。当然,长大后没小时候那么可爱了。

最后是两只迪莫,看时间圣光迪莫是 14 年 7 月 8 日出生的,皇家圣光迪莫是 14 年 7 月 15 日出生的。那大概最初的迪莫就是那只圣光迪莫吧,然后我慢慢培养,而皇家圣光迪莫应该是后面领的弄到满级。果然皇家是天生的啊,怎么感觉有点现实了呢突然。

点回家园,想起的音乐让我有点想流泪,是啊,就是这个音乐,主城的一切我都不熟悉了,但家园还是这个家园。

翻开宠物图鉴,水蓝蓝确实就最开始的时候最可爱,中间的波波拉和后面的水灵都好丑啊我去。不过好在最后圣水守护还算不错。

从图鉴上看,皇家圣光迪莫可以从圣光迪莫「超进化」来?

翻图鉴能想起一些往事。例如说这个超级瓦斯,当时看过视频,似乎有一种打法就是它第一个自爆了,让对方沉睡还是怎的?

如果说要让我从阵容中选一个最喜欢的宠物,我可能难以抉择,圣水守护是我的第一,迪莫是象征,幽王是门面。但如果让我选择一个最可爱的,我会毫不犹豫选择幽兰雪魅。

她真的好可爱,我从大耳帽兜开始就一直喜欢她了,每个阶段都喜欢。真的像养女儿一样,含在嘴里怕化了,放在手心怕摔了,她受伤心疼不得了,其他宠物基本没这个待遇。

冰龙王好像之前也在我的阵容当中,不过对它没啥印象了。

萌之王者也是,当时似乎在视频中也见到。

里奥这个没啥印象了,但当时也见不少。

原来是冥暗幽王是开学的活动。说起来这么看我其实沉迷时间也不长,撑死一年多两年吧,但当时玩的时间不短,确实体感时间挺多。而现在很多事情,忙忙忙,时间过得很快。

艾米似乎是迪莫的 CP?不熟。

然后后面还有一个公测活动,有个蛋弄出来公测喵喵。

一个轮回快到了……

快翻完了还没看到,搜了一下,圣藤草王,当时视频中也很常见,很帅,原来我也没有。

这个似乎是 VIP 才能到的地方?我还有一个非常操蛋的记忆。小学很小的时候我记得似乎是有一些小卖部卖的能充值?然后当时我似乎是 QQ 班级群群主,然后一个同学跟我 PY 交易了,我给他上了管理员,然后他给我充值了,也许是充值卡还是什么,反正我就是上了 VIP 弄了点东西。现在想来恨不得给自己两耳光。

差不多就到这里吧。

生死狙击

然后是《生死狙击》。这个居然没法玩,得下微端。服了,我开个沙盒。好吧,似乎有防作弊系统,不能装沙盒里面,只好委屈一下我的电脑了,我就装一会就卸掉了。

如果按连续投入时间算,《生死狙击》是我停留最久的一款游戏。从可能五六年级开始玩吧,一直玩到初一。然后说起来我初二以后就不再有像前面那番沉迷一个游戏了,结果我正好就是初二成绩开始变好了,初二后沉迷数学

当时玩得非常疯狂,一回来就是玩,甚至在电脑课的时候别人玩的是什么金山打字游戏,我跟其他同学玩生死狙击。我还记得我小升初那段时间的日记就写着,首先提了一下过段时间要期末了(六年级下学期),然后转头就写生死狙击了,令人绷不住了[8]。当然现在这个日记本也已经不见,无从具体查证写了些什么了。

那会我似乎还在学素描,可以说是我不堪回首的往事之一了。很小的时候在少年宫学画画,结果后面给哄骗去学素描,痛苦地学。真的好痛苦,我现在可以非常肯定,我不喜欢画画,即便我非常羡慕那些能将自己心中所想在笔尖呈现出来的艺术家,但我讨厌画画。

现在素描已经忘干净了,就记得个「明暗交界线」的名词,但我也不会画。然后我当时学素描跟家长的「交易」就是周末学完回家打生死狙击。唉,网瘾少年。

回归正题。虽然玩挺久了,但其实我甚至还没满级,才 80 多级。

打开仓库,引入眼帘的是七个黄金背景的东西,但其实我大部分不认识,因为其中有相当是我后面回归的时候领的、抽的。例如说上次回来看的时候随手开了一下军火库:

因为现在也不会玩了,水晶就不留着了,随手就开掉了。这是运气比较好开出来的。好像前面回归开过黄金的,不过黄金又好像是满进度条的,不是运气好中的。

最开始英雄级武器什么雷霆咆哮、炎魔之怒,后面出个 bug 一样的末日审判,再接着什么魔龙骑士,接着就烂大街了,正好我差不多退游了。凶神白虎记得是当时初一时的活动了,登录一段时间领的。而魔龙骑士好像是什么新的系统换的,忘记了,毕竟那时候新出的,然后正好我差不多淡了。

神器工厂不会给放弃了吧,怎么还是这老几样。翻了翻仓库我有几个燃烧系的,还有角色地狱行者。还有早期的暗月系列比较全,一套武器都有了,

看到个「火箭浣熊」,似乎是我第二个军火库开出来的?第一个是「G11 门神」,当时宝贝得不得了,这个甚至是我名字现在还记得,翻仓库还没翻到那里呢。

仓库还看到荣耀巴雷特、寒霜什么的。说起来我玩得比较爽的是狙击和刀战,步枪机枪什么的我不会玩,但是狙击连狙数人超神真爽。然后当时的双娇 AK47 荣耀和 M4A4 军魂我有后者。

还看到几把干将/莫邪,当时活动。还有什么绿魂、木剑啊。

我退游前有了个冒险模式,然后玩过一阵子,有黑刃、黑铳、地狱黑弩等,似乎就是从这里养的?还有什么寒霜高爆、M500 野火、蝰蛇。

还有个无人机,也是冒险模式的吧,忘了干啥用的了。还有什么激光剑、荣耀尼泊尔、白银尼泊尔,也许是后面活动抑或是哪里搞来的,挺传统的武器。

唉,我还给这个充过钱。跟洛克王国那个还不一样,是我自己偷偷充的,真是悔不当初啊。大概就是冒险模式出的那阵,初中吧,充了几次,给什么活动奖品吸引了。不记得多少钱了,但看进度条大概是 49 块。对当时的我来说还是算巨款的。

然后翻一下模式和房间,回忆一下。我比较菜自然是不怎么会玩天梯之类的排位的,只玩娱乐。玩得比较多的大概就是变异、刀战、大头英雄等了。看了一下机甲为啥还给标记为新啊,这我当时也玩过,似乎挺好玩的,但好像没多少人玩。然后躲猫猫也挺好玩的,不过也玩的不多。

变异其实又有几种,印象中我对原版感觉也一般,终极变异比较多?

还有一个,不知道现在还在不在。当时吃鸡比较火的时候我记得推出来过一个类似的模式,也挺好玩的。不过其实我没怎么玩过原版吃鸡。

翻了一下地图,第一个熟悉的就是圣诞游乐场了。这个确实是变异经典地图了,我变异模式也常玩,当年下架的事件,以及最后几个房间我也是有听说的。死亡沙漠也很经典,那个从侧面跳上去那里我还专门练过。

双塔惊魂这个常玩刀战,还记得有一处可以跑到底下守着,然后可以在别人跳过来还没落地瞬间卡点击杀对方,适合守。

梦幻工厂、失重仓库之类的没啥特别的记忆点,但很常见就是了,其实我不太感冒。暴力街区则是经典中的经典了,大头英雄什么的我基本上偏爱这个地图,很喜欢。角斗场则是刀战经典,直接复活就能开始拼杀了。

本来想回顾一下,随机匹配了进去了。然后突然意识到我现在是左手持标。不过好在没人,退了。

当时还看过不少攻略视频,不过与其说是为了学点什么,不如说是为了看别人炫技。一首背景音乐给我留下了深刻的印象——《死一样的痛过》(죽을만큼아파서),因为很好听,当时就尝试去搜索了一下。去翻了下,还真给我找到一个视频

还有一个印象深刻的是在一个地方用炎魔镇守,给我幼小的心灵留下来巨大的震撼。还真找到了

生死狙击还有一个比较重要的影响就是,是我参与社区的开始。很多论坛的术语、黑话,如「楼主」「沙发」等,我最开始就是从这里学来的。当然这是一段中二而不堪回首的往事了。

战神三十六计

逐渐退游后,又沉迷了一个叫做《战神三十六计》的网游,玩了一阵子。再然后就是时间久了这个也腻了,便也退游了。我的游戏沉迷史自此结束,后面不再会有相当长的连续时间玩游戏了,一直甚至到大学后假期都没玩游戏了[9]

看了一下,我之前玩的似乎「合服」了,看不了了。开了个新服,过了新手引导后终于可以看别的了,看了下魏王信息,怎么感觉还是那套阵容,没啥新东西?诸葛亮、华佗、吕布等。

哦,原来是我进错服务器了,终于找到我之前是哪个服务器了,进去瞅瞅。

想起来了,三国后变战国。再仔细看了看,我玩着真不少啊,好多都没啥印象了。翻了一下最后接收邮件的时间是 19 年 7.12,合着初二还在玩?看最早的邮件是 18 年 11 月,唔,那可能就是初二后才开始玩的,记忆错乱了。

Minecraft

这些都是网游,桌面客户端的游戏就比较少了。我印象中主要是 Minecraft、P 社游戏与文明系列。

首先是 Minecraft,之前是盗版 HMCL,然后之前周年庆的时候买了个正版。但到现在还没玩过。

翻了一下数据,我这里有两个 HMCL,一个是 2021 年的,一个是 2018 年的。前者是高一假期玩的,后者是更早时候玩的,符合印象。待我按顺序看看存档与记录。

一点开报错打不开,说要 Java 8-10,嗯我就记得当时要玩就得先下这个 Java。当时下的应该是 Oracle Java?安装时中间一个咖啡杯。

我先装一个 liberica8-full-jdk,装了 temurin8-jdk 结果提示还要 OpenJFX。终于能打开了,虽然提示有证书问题。

打开后映入眼帘的是我以前的网名,一个是 LogaN 倒没什么可说的,另一个,唉说起来还跟其他什么有点渊源。罢了不说了,开一把 1.14.4 Fabric。

在正式进入游戏前翻一下截图,有两百多张。90% 都是从 19 年 7.24 起(合着原来是到暑假玩 Minecraft 就顺带弃游三十六计了),一直到临近暑假结束的 8.29 期间截的。后面虽然偶尔还有截图,但基本不是生存存档的内容了,而是创造里面的,估计是复刻生电的东西。挑几张吧。

没错,我还取了一个名叫「怆陵」,你别管寓意,没有那回事儿。

养鸡场,网上抄的一个简单版。除此以外还有最朴素的那种四口放水的农场,以及观察者的甘蔗场。

还看到有几张应当销毁的,但舍不得销毁的。

感觉 19 年的想法跟我现在差不多,都是一阵子没玩了回来看看。不过那会我偶尔还会看看维基,还算能跟上版本更新的进度,现在我就完全不行了。

Minecraft 版本的印象我直接照抄当时买正版时写的了:

……看到 Minecraft 十五周年活动史低,在一番纠结过后,用 44.5 元入了正。

意义还是蛮大的,这大致是我印象中第一次为软件付费,也是第一次单纯地为游戏付费,尽管我其实很久没玩了。

根据我保留的截图来看,最后一次认真玩(抛开随便打开看两眼存档,回顾历史的那种,指认真玩生存)大概是 2020 年 3 月 17 日,也就是说,空窗期持续了我的整个高中。最终的版本是 1.14.4,是一个比较陌生的数字。

其实很早以前就玩过了,最早的印象应该是父母的 iPhone 手机上类似的游戏,现在想来应该是国内厂商抄袭的次品,没什么好玩的,好像就只有放方块啥的。然后后面慢慢接触了。

具体何时开始玩的已不可考,第一个玩的版本是啥也不清楚,但是第一个有印象的版本大概是 1.7,具体而言可能是 1.7.2,1.7.10 这个版本号似乎也有点印象(一开始想的是 1.7.12,想着 12 小版本号,但是经过考证似乎并不存在)。

然后后面可能经历过一段时间的脱坑,分隔点可能在 1.10,霜炙更新、探险更新在我脑海中好像跟之前的例如 1.9 战斗更新处于不同的记忆层次。

这次脱坑大概算是浅脱坑,虽然不玩,但是偶尔逛到更新内容,还是会去维基读上一读,之后的脱坑也基本是这样,持续到了 1.15 蜜蜂(1.16 知道是下界更新,但是没了解具体内容,后面的则是一概不知了)。

然后初二暑假,也就是 19 年 7 月 24 日(过去五年了啊……),新开了个档复坑,用的是 1.12.2,后面改用了 1.14.4,玩到了开学,然后 9 月回来几次,再就是 20 年再回来看几次了。然后就是再也没有开过新档玩了。

说实在的现在更新内容我已经看得一脸懵了,脱节太久了,即使再回坑也不会用超过 1.16 版本玩。

但其实我现在找不到回坑的契机,单纯买了吃灰。就当是为了童年买单吧。

看截图我四处探险,体验了很多新的特性,染了不祥之兆,也直接无准备去当过村庄英雄(自然那时手忙脚乱,血流成河)。

还做过英国国旗。

还屠过村……当然也过僵尸村民。

到暑假末期,我就四处探险了,去了很多地形,见到了狐狸、鹦鹉等。然后就结束了,我再没回来,回到这个 MY FIRST WORLD,但其实不是我第一个生存档的存档。让我时隔六年后再进入这个存档看看吧。

点进去我就知道没啥好看的了,创造模式在末地……不过还好我打过,回主世界看看。

差点忘了挂机的钓鱼机。

来一张居住地的远眺。更早的一个存档,我记得是在一个土坡底下建立基地,而后我基本就是直接住进村庄了。最左边是仿制的仙人掌机,忘记有没有用了,但好像每次开启都很卡,甚至有卡崩的情况。剩下的就不用多说了。

不过牧场怎么动物都不见了,刷掉了?凑近看了一下,都变成了掉落物。什么温暖的动物变成了冰冷的资源。

此外看了看地图,我标记了相当多的地点呀。

里面天色渐晚,我也退了出来。

接下来是 2021 的,具体时间上次登录是 2021.7.28,这时候的名字就是 Pilgrim 了。然后是 1.17.1,要 Java16+,又让我下载……看了一下,只有一个存档叫 Relax,没有屏幕截图留存。

一进去引入眼帘的就是我的俩傻狗,说实话我对这个档好像没啥印象了,等我探索一下。

不太懂这个档啊,似乎是完全在外面生存,外面一堆箱子都是挖矿等产物,还有个不认识的紫水晶,还有周围一群狗。然后存档除了上次死亡在附近外就只有一个 BOX,传送过去看了一下是在地下,但也没有很规整的痕迹。

这个档莫名其妙的,毫无印象。

P 社

我主要玩过的 P 社游戏大概有《欧陆风云 4》《钢铁雄心 4》《群星》等。当然了,都是盗版,我这会是有点不太关心版权的,毕竟那时候还小,我没有自己的钱,自然得用「学习版」。这段时间也见了如 52PCGame 等 Discuz! 论坛,听说了「三大妈」等。

这其中哪个最好玩我不好说,但游戏体验最差(很快就不玩了)的大概是 HOI4,因为我完全不会玩,EU4 都有去打 mod 了说明感觉还挺好玩的,HOI4 玩不明白。

这也是 2020 年的,具体来说是 2020.2.28。那会还发了空间(虽然后面删黑历史时删掉了),这是其中两张,配「诗」一首:陈桥兵变赵匡胤,三三有为当皇帝,篡位继续攻汗国,神君再披军大衣。

群星也挺好玩的,我还记得似乎是初中的时候,那会笔记本电脑放在客厅茶几那,在母亲面前玩,还问我玩的啥。不过可惜的是这些似乎都没有存档、截图留下,有点可惜。

不过有一个 22 年初寒假记录的内容,我搬运一下:

……然后应该就自定义了大陵帝国,在 EU4 驰骋。时间是 907 年,大陵帝国占据了大梁的左大半边,陵太祖名为「符成」(挺搞笑的是后面也有一个皇帝的名字叫符成)。然后就是打游牧,再逐渐统一天下,还有远征萨摩亚、阿拔斯。「和平发展」与「掠夺屠城」是发展的主流操作,两者相互辅佐。挺可怜也可笑的是曾经的第二列强——强大的阿拔斯王朝,吃了阿克苏姆王国后(应该是这个原因,因为我那段时间禁屠城了,阿克苏姆大概两千发展度),叛军四起,加以外敌入侵,被打爆好多次,还迁都了,堂堂大帝国,举国上下只拿出了 2k 延敌,即使在和平的时候也才 4k 人平叛。腐败高居 100 左右,多次破产。距离我的弃坑点已经三十年了还没恢复元气,实在让人唏嘘。EU4 就不多说了,毕竟控制台操作还是挺表脸的,这点不如文明。

听说欧陆风云五要出了?之前还听说过维多利亚三等,不过现在也都没玩了。

Steam 记得之前 CK3 有限免过?领了,但其实我没玩过,只听过「我女儿的女儿还是我的女儿」这句,还在初中的时候分享给了同学(历史课正好学「我封臣的封臣不是我的封臣」)。

虽然我有 Steam 账户,但其实没用过 Steam 玩游戏,里面除了 CK3 好像也就几个免费游戏,不认识。因为我大多都是玩的学习版,要不是后面要创意工坊等,我估计都不会注册 Steam。这大概就是我跟现在身边玩游戏的人一个比较大不同了——我不用 Steam[10]

现在手机翻上一翻,还能找到 Steam 恢复代码的屏幕截图,时间是 20 年年底。

文明

P 社数据之类的,如果我没记错的话是在「文档」里面,现在找不到(OneDrive 同步),看来是之前某一次玩太久后,痛下决心直接删掉了。

之前盘里还有存 EU4 等学习版,后面也跟着删掉了,再随着后面盘结构的重新整理而被彻底清理掉了,仅 Minecraft 幸存。

而同时被清理掉的还有文明,包括《文明 5》与《文明 6》。

补充

Epic 后面发了免费文明,领了。当然,这个甚至不如 Steam,Steam 好歹装到电脑过。

上面的记录中同期还记录着文明,正好是上一段:

……然后就进入了「席德的文明」世界,以「阿基坦的埃莉诺」(法国)之身统一一个大陆,与另一个强大的文明隔海对峙(忘了名字,那个文明的大陆上还有朝鲜和印度两个小国,都很弱),然后就弃坑了。其实游戏胜负已经决出了,我所在大陆的一个国家(还是我的友好国家)「文化胜利」了,直接喷血,更气人的是,在文化胜利前本来友好协定已经过期了,然后我还去续签了!于是我拼命买它作品,有时还有几回合文化胜利就不显示了,我还以为抑制住了。结果最后给我当头一棒,直接胜利。它胜利后不久友好协定过期,我直接飞机扔热核装置到它首都,然后末日机甲踏平它全国,气死我了!还有一个挺有意思的:铀矿极其稀缺,即使统一大陆了铀矿的产出也支撑不了两个末日机甲的消耗,于是我盯上了隔壁大陆的一座自由城市。那座城市本来是印度的,印度被那个强大文明暴捶后独立了,然后我千辛万苦拿到手了,因为是孤城,三回合就又独立了,然后我两个坦克单位和一个海军单位(忘了是啥)就再次收复,耗了几回合后,旁边一座印度城市也独立了,然后归属于我了(因为那座城市被强大文明、我的孤城和海洋包围了),然后我的两个坦克单位分居两城,重复着「独立 \rightarrow 收复 \rightarrow 独立」的循环,即使总督上任后也持续了一段时间,后面终于消停了,忠诚度变化量终于不是负的了。

可以看出来网课给我玩爽了,P 社、文明轮流伺候我。

其实我感觉五、六都挺好玩的,当然现在我也具体忘了有啥差异了,无法从中选择一个我更喜欢的了。

但我记得一个模式,类似大逃杀,单枪匹马,然后有一个毒圈,可以「招兵买马」。查了一下,似乎叫红色死亡。这个挺好玩的,而且用时相对比较短,不会从九点一直玩到八点。我后面懒得开一把新的就是玩这个解解馋。再然后就是被删掉了,尸骨无存。

至此,游戏部分基本上结束了。而大学后再也没有过去这样的游戏经验了,大概我上面记录的 22 年寒假,就是最后一次我痛痛快快地玩游戏的时光了。

破解与资源获取

上面的内容顶多是让我对计算机有所接触,但远远称不上是有所兴趣,或者说兴趣其实是在游戏上面。但让我真正误打误撞闯入计算机世界大门的,我想应该是——破解

与其说这是「破解史」,不如说是「资源获取能力形成史」。为了这个目标,我在网络世界摸爬滚打,踩过不少坑。也正因如此,我没有成为一个计算机盲,起码对基础操作如解压、改名等不会感到陌生。

这里的资源的确主要还是破解资源,主要应该就是破解软件和破解游戏。

破解软件很多啦,各种常见大型软件,我之前也有所提及。破解游戏主要就是上一部分的后三大件了。

当然当时还是很勇的,无知者无畏。破解的东西毕竟是来路不正,我就敢随随便便打开、运行,现在看来确实是胆子够肥。换现在我是压根不敢这样的。

当然并不是没有代价的,我也有下过莫名其妙的下崽器。一开始还傻傻得认为安装后真的就有了,到后面见多了就明白下到这个就算是废了。

一开始就是百度搜名字,然后去各种现在嗤之以鼻的资源园下载,还会点什么「高速下载」。随后就逐渐学聪明了,不能点,得点原始的下载。

这些知识都是没有人教的,都是靠自己走一遍踩坑的。我上面也只是简单提了几点,实际上有很多属于是已经成为未被察觉的感觉了,我无法一一列举。

资源获取一个很关键的途径就是各种各样的论坛了,比较古早的一般是 Discuz!,例如上面提到的 52PCGame,还可能有更为现代的 Discourse,如我后面会提到的取代了掌上百科FreeMdict 等。

还有一个比较有意思的途径是「知乎」。我当时其实挺热衷于在知乎上搜一些「小众软件」「宝藏软件」的,不仅仅是看各种故事会和吹逼。这也算是我当时认识工具的一个途径。

这是一张五年前我初三网课时的屏幕截图,不知怎得当时截了张图存在了 OneDrive,也是仅存的一张。这可以算是为研究那时候的我提供了宝贵的材料。

当时会把很多东西堆在桌面,而常用软件类型各样,于是用上了腾讯桌面管理进行分组。可以现在来看看有什么。

首先是 Study 分组,有 Wolfram Mathematica, MathType 这两个破解软件,以及 GeoGebra 一个免费软件。下面可能还有一个,有的话可能是几何画板(破解版)或是 Desmos,不确定。

Wolfram Mathematica 是一个大型数学软件,提供非常多强大的功能,只是我那时候更多的是当作一个 10+G 的计算器;MathType 是一个数学公式编辑器,用于创建一些数学公式,这部分会是后面涉及到的重点;GeoGebra, Desmos 和几何画板都是画图软件,现在的我认为 GeoGebra 最强,也是现在唯一用的。当然其实我现在已经跟数学脱节了,离这些工具就更远了,但依旧是留在电脑上(Wolfram 与 GeoGebra)。

然后是 Communication 分组,有 QQ 这个最常用的社交软件,当时还没微信,说明微信用的相当少,符合印象;有钉钉用来上网课,打码的内容其实就是在网课(甚至其实是在考试);还有两个 Edge。比较令人意外的也许是 Steam,我虽然有 Steam,但我其实不用,里面好像就几个免费游戏?但也从来没玩过。玩过的游戏都是离线破解。

接下来是 Function 分组,有 Everything 这样的必装工具用以取代 Windows 搜索,有火绒用来杀毒和清广告弹窗,有 IObit Uninstaller(破解)用来卸载软件,有 IDM(破解)与 FDM 用来下载软件,有网易词典(疑似破解)用来查词等,下面还有啥不知道了。现在的我不同之处有卸载软件换成了 Bulk Crap Uninstaller,因为免费开源不用去破解;去除了 FDM 因为效果不佳;查词用了 GoldenDict 加搜集的词典资源等。

旁边可以看出来还有一个分组,但就露出来个 Stellaris。这个分组大概是游戏分组,被挡住的也就几种可能——EU4, HOI4, 文明, MC 了。

再下面的 Office & Adobe 分组不用提,可以直接看下面的 Tool 分组,可以看出来当时的我还是相当稚嫩呀。按键精灵用来连点(为了干啥忘了),steamcommunity 为了上 Steam,更准确来说是为了上创意工坊等等。不过更为引人注目的大概是那个 Ollydbg Pro。没错,看这个图标,这个是从吾爱破解里面搞来的。不过其实我一直没有账号,因为不登录也能浏览搜索部分贴子。但之前,大概是 24 年周年开放注册了,拿了个号,现在还在、没给回收。

当然你要问我懂多少汇编,我只能说我现在也不太懂,当时那更是天方夜谭了。装模做样地下一个,只是为了「撑场面」,给自己看。但也是从这里我第一次见了 NOP, JMP 这俩我现在还记得的命令。如果说我现在是一株禾苗,那这里就算是播下了一颗种子吧。

学习与笔记系统

我想了一想,大概也是时候进入到我高中的学习中了,这部分可以说是最为重要的阶段了,是我破土而出,成为嫩芽的阶段。

Python

在此之前不得不先提一下我的编程基础,其实是零耶!

没错,我上面折腾了很多,但其实我没学过编程,甚至可以说是没接触过。

我听过的第一个编程语言,就是 Python。「生活苦短,我用 Python」这句宣传语,也是从我的初中同学口中听说的。当时有两个朋友 C 与 V,后者向我介绍过,并提到过他写了爬虫之类的,前者是讨论的时候我了解的。不过当时我其实并不感兴趣,也没放在心上。

不过后面我也不知道为啥,决定开始学习 Python 了。翻了一下现存资料,21 年暑假有一本 MOBI 格式的《Python 编程从入门到实践》,同时我还有 Markdown 笔记(手抄书)。不过根据印象,其实更早的时候就有学习过了,只是也许有些遗忘了,因此再次拾了起来。

说起来高中电脑课是不是也学的 Python 来着?但我记得学的时候我就已经基本会了基础了。

我记得当时大概在公交车上面的时候,就会读一下这本书,了解了 Python 的基本语法。对了话说回来,用的是静读天下,破解版。同时期读的书大概还有《普林斯顿微积分读本》,虽然没读多少,但大致有点概念了。想到这的时候我又有点不确定了,因为我记得似乎这本读得挺早的,但起码是初二、三后,不可能是初一与初二早期就是了。

数学

我一直挺喜欢数学的,起码在我成年前是这样的,也许是叶公好龙。

小学、初中以及高一高二的时候我还是班级的数学课代表,令人感叹。

小学的事情就不必多说了。而初中对数学的喜爱与探索有一些记录留在了 QQ 空间中。这些内容其实在高三时候「2022 年 9 月 25 日胡思乱想」中有所记录。

不知怎得,在初二时候的 18 年,开始回归 QQ 空间了。在此之前更是年少无知的童稚时期,是各种旅行照片[11]。而 18 年国庆那一天,我突发奇想,时隔一年多发了一张自己做的煎蛋照片,后面就开始以不低的频率「记录生活」了。

然后 11 月一次考试后,见到数学试卷上的「秦九韶公式」,甚为惊奇。于是后面尝试证明了一下,将稿纸贴了出来。

虽然比较丑陋,但毕竟是历史,还是贴出来见证一下起点吧。

而后我又在 QQ 空间中分享了许多东西,学习内容有地理等,但主要还是数学;也有记录生活,如春节火锅、花、狗等;还有网上看到的东西,转发来故作深沉……

这是有记录以来第一次用几何画板,19 年 1 月。这是一次考试的一道压轴题,大约是期末?没做出来,耿耿于怀,于是后面重新再做了一遍,将过程记录了下来发在了空间。虽然用了几何画板,但过程却是用截图工具的文本功能弄的,比较丑陋。

第一次有记录的用 GeoGebra,2 月。不过过程还是用的截图工具。

3 月,用几何画板自己的文本功能写了东西。源头是我注意到两圆相交的部分,于是想研究一下,稿纸写了点内容,后面用几何画板画了出来,命名为「端圆」。而后还有一些补充,弄了不等大小的两圆的情况。

8 月的一条说说弄了一个简单的微积分,凑了一个数值,第一次记录了使用 Mathematica。

想起来我对微积分的初步了解源自一道「定义新运算」题目,给出来好像是 baxn ⁣dx=an+1bn+1n+1\displaystyle \int^a_b x^n \d x = \dfrac{a^{n+1} - b^{n+1}}{n + 1},然后求值什么的。我当时就非常惊奇,这个「撬棍」似乎就隐藏了许多奥秘。我想也就是为了搞明白这个,我后面找了《普林斯顿微积分读本》,学了点微积分的基础知识。

如果是现在的我可能会这样问 AI「有个光滑的数学符号,上面是 aa,下面是 bb,然后是 xn ⁣dxx^n \d x,这是什么意思?为什么它等于 an+1bn+1n+1\dfrac{a^{n+1} - b^{n+1}}{n + 1}?请你详细解释说明一下,我没有微积分相关基础。」然后它可能回复「你的观察非常细致!让我们从一个比喻出发,窥探微积分的奥妙……」。很是羡慕,如果我当时也能有这样的工具满足我各种各样的想法与求知欲就好了。

手机上的 GGB,我记得挺难用的。说起来我当时的字体也是花里胡哨的,现在看起来挺难受的。11 月考前用尺规在圆内接了正三角形与正方形,然后想求一下这个重合面积,考完试后用来验证。

然后是一个由生活中事情抽象成数学问题的例子。我初中上学路上有一个红绿灯,两边是大概四分之一圆弧的弧状边界。然后我就突发奇想,也许我等红绿灯时,不需要在那里过马路,而是可以往外站一点,虽然过马路的直线路程更长,但走的弧状路程更短,也许可以让路程减小。于是我将其抽象成图形,并进行了求解,希望验证一下。尽管如此我最开始的猜想是在边界路程最短。还记得当时是在运动会,我就在棚子底下拿着草稿纸演算。11 月将观察到的内容与计算发了说说。

我假设两边是全等的圆,半径都是 rr,两圆圆心距离 2r+s2r + s(即马路长度为 ss),然后假设夹角为 θ\theta,最终算出来的路程是 s(θ)=πθr180+s+4rsin2(90θ2)s(\theta) = \dfrac{\pi \theta r}{180^\circ} + s + 4r\sin^2 \left( \dfrac{90^\circ - \theta}{2} \right)

没错,当时不会弧度制,因此用的是角度表示。如果用弧度制表示的话就可以进一步化简为 s(θ)=θr+s+4rsin2(π4θ2)s(\theta) = \theta r + s + 4r \sin^2 \left( \dfrac{\pi}{4} - \dfrac{\theta}{2} \right),然后可以再用三角函数的推论化简为 s(θ)=θr+s+2r(1cos(π2θ))=θr+s+2r2rsinθs(\theta) = \theta r + s + 2r \left( 1 - \cos\left( \dfrac{\pi}{2} - \theta \right) \right) = \theta r + s + 2r - 2r \sin \theta,直接求导得 s(θ)=r2rcosθs'(\theta) = r - 2r \cos \theta,因此最小值当 θ=π3\theta = \dfrac{\pi}{3} 时取得,即 θ=60\theta = 60^{\circ},与 r,sr, s 都无关。

上面的推导是我在上高一后不久完成的(2020.12.19)。而我在当时观察到最值并非边界,而是在 6060^{\circ} 时感到非常惊奇,还用 GeoGebra 用我所能提供的最大精度画了图验证。

20 年 1 月,还是 GGB,这次搞出来一个爱心的局部,不过并非是我那个说说的重点。重点其实是我在研究弓形面积(弦与弧围成的部分),这是输入表达式的时候弄出来的图形。另外这时候图片格式开始是 PNG 了,可能换了截图方式或是保存格式?此外还能注意到我右下角的输入法,大约是搜狗吧。

2 月,有一点极限的想法了。

3 月,大概是封控的网课时期,用数学方式推导了点透镜的知识。哎呦我去。

5 月,研究二次函数的时候发现了「准线」。当时随便选择两个点在二次函数(即抛物线)上,然后作关于抛物线的切线,并令这俩切线垂直,然后我惊讶地发现交点似乎总是落在一条直线上。同时我通过修改参数与瞪眼法,观察出来这条直线有上界:对于二次函数 f(x)=ax2+bx+c,a>0f(x) = a x^2 + b x + c, a > 0,准线似乎不会超过 y=cy = c。这个结论可以说是高中解析几何比较重要的一个二级结论了。

一周后的考试,一道阅读材料给出来焦点准线的定义,随后我得知了准线方程实际上是 y=cb2+14ay = c - \dfrac{b^2 + 1}{4 a}

不过此时的我还没啥感觉,只是有一种疑问被解答的舒爽感。只不过两个月后的中考数学最后一题就涉及了焦点准线,而我没做出来,悲剧。

6 月跟朋友在游园会一起来了深中,晒布,也是我唯一一次参加深中游园会活动(高中都没参加)。这里引用之前弄过的图片。

然后是我的初中集大成之作——虽然有点烂尾——我在临近中考的 7 月初制作了一套试卷,现在 Word 文档还保存着。

当时心中其实有几道比较精妙的题目,但没记录下来,于是淡忘了,只有一道最终组了进去,其他的基本上是凑数的,甚至最后想不到别的了网上搬了题目,好歹是凑够了 12 道选择。

第四题其实有点问题,可以看到英国国旗并不正确,不符合圣帕特里克十字。

第七题就「夹带私货」了。第八题就是我唯一一道精妙的题目,只可惜我自己也没有标准解法,只有答案。

第十题没什么,为了凑数加入的纯粹的硬算题目。

第十二题当时是这样说的:「9.12.是网上copy的,因为没时间想了,12有思路但我还不会做就还不能出,有时间改还有出填空和后面」。到现在我都不知道我那心中想的 12 是什么。同时也能看出,我的「有时间」就是「没时间」。

7 月底,稀里糊涂地去深中自招了。这是真莫名其妙啊。摘录当时说说的一段:

2021.7.23

我报错了。
招40人1班(参加面试200人)的是省班(仅限D类),要学生自荐、学校推荐等发给深中学校邮箱的。
所以我报的是ACD类都能报的省班,招200人4班(参加面试1000人,难怪名单那么多长)的市班。
我今天才搞明白
不过也没关系了市班就市班。
7.27去面试,地点大概(还没找到)是深中新校区吧。
祝我成功!!!

可以说真的是糊里糊涂,要求都没看明白,但最终还是注意到了。之前这样讲的时候一直有人问我,明明省班名额 40,市班名额 200,那应该是省班更难才对,为啥当时想报省班呢?其实当时我是这样想的,省班就 D 类能报[12],那能报的人就少了,而市班可能会有很多大佬都报名,那还有我啥事。不过其实当时也是没看清要求,想的是省班结果最后报的是市班。

考完后回来玩 Mathematica。

8.5 出成绩,挺一般的,当时想着估计就寄了,深中无缘了。自始至终没想过自招,因为本来就是打着见见世面的念头。然后两天后的 8.7 就收到喜讯了,擦线录取。

我现在仿佛还能感觉到当时的喜悦之情,公示文件后,一搜自己的名字,高亮地躺在那里,那种突如其来的惊喜,呀。名单中很多名字我现在也是了解了,要么是后面几年的同学、要么是让我仰望的大佬,或兼而有之,等等。

尘埃落定后,我再度望向了滑铁卢,弄起了那道焦点准线题。不过说没数学思维吧,我设了好多变量硬解,说有数学思维吧,我没注意到坐标系可以移动,从而可以消掉两个参数(即发现抛物线的标准方程)来简化运算。

另外这是 MathType 第一次出现。我现在了解到,是不是 MathType 其实是用在如 Word 里面输入公式?但其实我这个是全部都在 MathType 里面输入,然后导出 GIF。后面就是导出 JPG 了,因为更清晰。这些文件,包括原始保存的 WMF 文件,我现在都还留着。

正好翻到个有意思的,当时网上看到一个历史题目,于是转发到空间:

拉什莫尔山国家纪念公园——也称总统山,上面雕刻着美国历史上影响深远的四位美国总统。其中第一位是华盛顿,第二位是杰斐逊,第三位是罗斯福,第四位是林肯。请问第三位的功绩是( )
A.实行罗斯福新政,减小了经济危机带来的损失
B.调停日俄战争,获得诺贝尔和平奖
C.领导独立战争,实现美国独立
D.颁布《解放黑人奴隶宣言》,解放黑奴

一个朋友回答后评论了一句:

应该加上特朗普,因为他促成美国解体,使世界和平

哈哈哈哈哈哈。

一直持续了相当一段时间,当初甚至开学后每周似乎都会做一点。

高一体育嘉年华做了志愿者(我有点忘了志愿时长有啥用),然后下面是高二时回来的留言,看了两天小说。

冬至图书馆开门,当时还不是很完善,新校区嘛,书全是数学类的,于是我看了点书做了笔记,上面的就是看《微积分学教程(第二卷)》记录的(因为第一卷当时被人拿走了)。这个本子大概也已经失踪了,因为我没看多久就停了。

我还记得那会晚上食堂免费发汤圆,结果很多人晚自习迟到早退去领,导致被迫内卷,最终我当时排了十五分钟吃了五个芝麻馅汤圆。

如果是现在的我,大概是不会愿意凑这样的热闹的——不就是汤圆嘛,排个长队浪费时间——兴许会这样想。不过我偶尔也会做一些否决我「理性」判断的决定,因为当我现在回想起来的时候,不是质疑我当时干嘛这样浪费时间,而是嘴角上扬、微微一笑。这也足够作为我这样做的动力了。

Anki

2021.1.23

众所周知,生物是文科。

一个下午,生物书才看完一半

如果有天堂,那里应该是图书馆的模样。

上面两条是同一天发的两条说说。我那会还有偶尔周末去图书馆的习惯,不过其实很勉强称作是习惯吧。前一条是 16:34 发的,大概是在复习生物,后一条是 19:00,我还记得是晚上离开图书馆,扶梯的时候发的。

如果我印象没出太大问题的话,从这时候我就又开始构想我的 Anki 知识体系了。但这时候还只是装了软件,建立了牌组,还没开始正式制卡和刷卡。

Anki 我就不多介绍了,主要是在知乎受到了一些文章与回答的影响。现在想来也是惊奇,当时 Anki 虽然也有长久的历史了,但似乎看起来还是有点简陋,而现在它还在快速迭代发展,已经跟当时判若两物了。

现在想来其实现在的我跟当时也很相似,都有点「完美主义」倾向,都想要先把计划准备得周密完全、没有疑问点再开始。我现在即便是有 AI 的助力,依旧是能拖上一个学期才把学期初规划的内容付诸实际。

重新打开之前保存的卡组数据来看,我第一张一直保留到最后的卡片制于 2021.5.5 10:31(当然,在此之前似乎也有制卡?只是可能后面废弃移除了),距离最开始的构思过去了差不多四个月,一个学期。毕竟我当时其实只有周末可以研究了,何况我又犹豫不决,徘徊不定。

而最后一张制于 2023.6.9 11:11,没错,就是生物考试前的中午,又制了点卡刷卡,临时抱佛脚。具体时间是从 10:40 开始,一直制卡半个小时。如果我没记错的话是在图书馆二楼隔间角落的那个座位制的,共 25 条笔记 31 张卡片。

这就是第一张卡片了,是熟语卡片。不过这个模板其实也经过我反复迭代修改,这是最终版本。右上角的字典,点了后可以打开欧路词典,因为我主要是在手机上背嘛。但其实似乎没怎么用过?

我的模板基于 Leaflyer 的模板知识点模板 v3.0,但现在已经魔改了很多了。

看了看热力图,第一条复习记录也是来自 5.5,不过这也正常,删了估计复习记录也不会呈现。而总计有 1.2w 笔记,1.6w 卡片:

科目 笔记数目 卡片数目
语文 3117 3517
数学 406 520
英语 3832 4664
英语作文 301 301
英语 Extra[13] 2148 3750
物理 357 486
化学 458 744
生物 1438 2022
总计 12057 15984

看着吓人,实际倒还好啦。有的是测试卡,有的给搁置了,毕竟不是所有卡片都有用来背,有的后面被认定为了废卡,只不过没删除罢了。

这是文言文重点字词的卡片,一共 1890 张,基本是把课内的古文下面的注释都囊括进去了。说起来我语文唯一一次上 130,就是高三第一次考试,当时不是高考标准的卷子,考了很多古文释义选择好像,加上当时作文写得不错,结果给我拿了一次 130。这也是我高三语文的顶峰了,后面一路下滑,到最后的折戟沉沙。

虽然很多是在家里做的,但其实后面还尝试在学校制卡。因为图书馆提供了电脑,于是我就从 GitHub 那边整下来提前准备好的东西,然后装上 AutoHotkey 开始制卡。脚本现在还留着:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#Requires AutoHotkey v1.1+

Global pinyins := "\w*[āáǎàōóǒòēéěèīíǐìūúǔùǖǘǚǜü]\w*", Board := {"Word": "", "Definition": "", "Sentence": "", "Prefix": ""}, Temp := ""

Clean(string) {
    if !(string ~= pinyins)
        string := RegexReplace(string, "[\w\d\s@#]")
    return string
}

Get(sentence := 0, prefix := 0) {
    Clipboard := ""
    SendInput {Ctrl Down}c{Ctrl Up}
    ClipWait 0
    if sentence
        Board.Sentence := Clean(Clipboard)
    else if prefix
        Board.Prefix := Clean(Clipboard)
    else if Board.Word
        Board.Definition := Clean(Clipboard)
    else
        Board.Word := Clean(Clipboard)
    Clipboard := ""
}

Put() {
    if Board.Word {
        SendInput % Board.Word
        Temp := Board.Word
        Board.Word := ""
    }
    else if Board.Definition {
        SendInput % Board.Definition
        Board.Definition := ""
    }
    else {
        SendInput % "{Text}《" Board.Prefix "》:" Board.Sentence
        Temp := ""
    }
}

f1::Get()
f2::Get(1)
f3::Get(0, 1)
f4::Board.Word := "", Board.Definition := ""
f5::Board.Word := "", Board.Definition := "", Board.Sentence := ""
f6::Board.Word := "", Board.Definition := "", Board.Sentence := "", Board.Prefix := ""
f7::
msg := ""
for index, value in Board
    msg .= index ": " value "`n"
MsgBox % msg
return

#IfWinActive ahk_exe anki.exe

f1::Put()
f2::
loop 3 {
    Put()
    SendInput {Tab}
}
return

当时搞到了语文课本的矢量 PDF,于是就写了脚本,快速地复制并制卡。我就像流水线的工人一样重复地劳作,但不觉疲惫,因为真要说的话,在我对 Anki 祛魅后,这就是其对我作用最大的地方了。

一块板(Board)包含了四个部分,字词、定义、例句与出处,这个脚本就是为了方便地从 PDF 格式中复制过来的内容,自动提取成 Anki 期待的格式。

因为我是一篇一篇、一句一句做的,即出处的层次大于例句,例句的层次又大于字词,因此在只变化字词的时候可以固定二者,这样就不用重复输入了。

当时从 PDF 复制东西出来会有乱码,因此也有 Clean 来进行清理。

另一个实例是英语的派生卡片,416 条笔记 424 张:

也有脚本来加快我的人工流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#Requires AutoHotkey v1.1+

; Board 版派生捷径
;;; 使用 Borad 储存复制结果。效率高,性能好,兼容性较好。
;;;;; 全局捷径
;;;;;;; Ctrl + Q       跳转到 Add 窗口
;;;;;;; Ctrl + W       跳转到 Browse 窗口
;;;;;;; Ctrl + E       跳转到 Anki 主窗口
;;;;;;; F1             储存内容到 Board
;;;;;;; F2             清除 Board 内容
;;;;;;; F3             获取 Board 内容
;;;;; Anki 捷径
;;;;;;; `              保存卡片
;;;;;;; CapsLock       输入 <br> 并换行
;;;;;;; F1             格式化输出(词性智能空格 & 标点智能替换为中文)
;;;;;;; F2             格式化输出(自动添加 Cloze)
;;;;;;; F3             自动化操作(清除 Board)
;;;;;;; F4             自动化操作(保留 Board)
;;;;; 自定义捷径
;;;;;;; 添加 <key>::<command>

SetTitleMatchMode 3

Get() {
    Clip := ClipboardAll
    SendInput {Ctrl Down}c{Ctrl Up}
    ClipWait 0
    content := Clipboard
    Clipboard := Clip
    return content
}

Clip(Text) {
    Text := RegExReplace(RegExReplace(Trim(Text, " `t`r`n"), "([a-z]+)\.\s?", "$1. "), "(*UCP)\s+", " ")
    for e, c in {",": "", ".": "", "?": "", "!": "", ":": "", ";": "", "(": "", ")": "", "[": "", "]": ""}
        Text := RegExReplace(Text, (e ~= "[([]") ? ((e ~= "[.?()[\]]" ? "\" e : e) "(?=\s?[\x{4e00}-\x{9fa5}])") : ("(?:[\x{4e00}-\x{9fa5}]\s?)\K" (e ~= "[.?()[\]]" ? "\" e : e)), c)
    return "{Text}" Text
}

Put(clean := 1) {
    for index, value in Board {
        if (index < 3) {
            SendInput % Clip(value)
            SendInput {Tab}
            continue
        }
        if Mod(index, 2)
            SendInput % "{Text}{{c1::" Trim(value, " `t`r`n") "}} "
        else {
            SendInput % Clip(value)
            SendInput {Text}<br>`n
        }
    }
    SendInput {BackSpace 5}
    Sleep 1500
    SendInput {Ctrl Down}{Enter}{Ctrl Up}
    if clean
        Board := []
    return
}

Global Board := []

^q::WinActivate Add
^w::WinActivate Browse
^e::WinActivate PilgrimLyieu - Anki
f1::Board.Push(((Clip := Get()) != "") ? Clip :)
f2::Board := []
f3::
msg := ""
for index, value in Board
    msg .= index ": " value "`n"
MsgBox % msg
return

#IfWinActive Add

f1::SendInput % Clip(Board.RemoveAt(1))
f2::SendInput % "{Text}{{c1::" Trim(Board.RemoveAt(1), " `t`r`n") "}} "
f3::Put()
f4::Put(0)
+CapsLock::SendInput {Text}<i>eg.</i> `

当然现在我已经看不明白怎么弄了。

对了,右上角除了词典外还多了个发音,点了后会有有道的语音,只不过因为需要在线同时我不太喜欢外放而显得有点鸡肋。

除此以外,其实上面的派生是打乱的,同时词性也是自动斜体的,原本 HTML 如下:

1
2
3
{{c1::unnecessary}} adj. 不必要的;多余的<br>
{{c1::necessity}} n. 必需品<br>
{{c1::necessarily}} adv. 必然地;不可避免地

这是为了防止我只是记住了位置,而不是真正弄懂了它的派生。在模板中添加了这样的脚本来实现打乱:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function shuffle(array) {
    for (let i = array.length - 1; i > 0; i--) {
        let j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
}

var content = document.getElementById("inner").innerHTML;
var contents = content.split('<br>').filter(function(s) {return s && s.trim()});
var mapping = Array.from(new Array(contents.length).keys())

shuffle(mapping)
sessionStorage.setItem("randomIndexes", JSON.stringify(mapping));
document.getElementById('derive').innerHTML = mapping.map(item => {return contents[item]}).join('<br>')

除此以外还有词汇卡片、翻译卡片和积累卡片,加起来四千多张。首先是词汇卡片,这种只有看词猜意:

然后是比较帅的积累卡片,除了看词猜意外还有看意猜词(翻译卡片就只有看意猜词):

这是因为我苦于有很多意思相近的单词或短语,如果直接遮住让你猜,常常会很难知道是要你答哪个。因此我就做了一个这样的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
function auto_spell(word) {
    let hiddenWords = ["?", "in", "of", "from", "at", "on", "through", "via", "before", "after", "until", "to", "down", "up", "off", "away", "into", "onto", "upon", "out", "with", "without", "within", "by", "for", "since", "toward", "towards", "forward", "forwards", "above", "below", "beyond", "again", "about", "under", "beneath", "during", "around", "than", "over", "beside", "behind", "as", "and", "or", "though", "but", "not", "nor", "either", "neither", "yet", "both", "so", "such", "that", "which", "when", "how", "what", "a", "an", "the", "this", "that", "if", "no", "most", "one", "one's", "oneself", "do", "does", "doing", "did", "done", "be", "is", "are", "was", "were", "been", "many", "much", "can", "can't", "sb's", "back", "there", "there's", "it", "it's", "other", "else", "among", "against", "things", "ever", "something", "everything", "anything", "i", "me", "my", "we", "us", "our", "you", "your", "he", "him", "his", "she", "her", "they", "them", "their", "whether", "whose", "very", "other", "others", "another", "upside", "should", "only", "ahead", "throughout", "once", "all", "b", "still", "what's", "aside", "some", "each", "apart", "like", "every", "will", "sometimes"]
    let preservedWords = ["sth", "sb", "sp", "br"]
    let underline = "__";
    let partword = "";
    let instruction = word[0];
    let result = ""

    if ([":", "-", "!", "#"].includes(instruction)) {
        word = word.slice(1)
    } else {
        instruction = null
    }

    if (instruction === "#") {
        return underline.repeat(2);
    } else if (instruction === ":") {
        let params = word.split("_")
        for (const order in params) {
            const param = params[order]
            if (order % 2) {
                result += param ? underline : ""
            } else {
                result += param
            }
        }
        return result
    } else if (instruction === "-") {
        let params = word.split(" ")
        for (const order in params) {
            result += underline + " "
        }
        return result
    } else if (instruction === "!") {
        hiddenWords = []
        preservedWords = []
    }

    for (let i = 0; i < word.length; i++) {
        let char = word[i];
        if (/[-\w'?]/.test(char)) {
            partword += char;
        } else {
            if (hiddenWords.includes(partword.toLowerCase())) {
                result += underline;
            } else if (preservedWords.includes(partword)) {
                result += partword;
            } else if (partword.length > 1 && !(/^\d/.test(partword))) {
                result += partword[0] + underline;
            } else {
                result += partword;
            }
            partword = "";
            result += char;
        }
    }
    if (partword) {
        if (hiddenWords.includes(partword.toLowerCase())) {
            result += underline;
        } else if (preservedWords.includes(partword)) {
            result += partword;
        } else if (partword.length > 1 && !(/^\d/.test(partword))) {
            result += partword[0] + underline;
        } else {
            result += partword;
        }
    }
    return result
}

document.getElementById("hint").innerHTML = auto_spell(word)

当然,其实这个不是我写的。当时 ChatGPT 刚出,New Bing 紧随其后,我是让 New Bing 写的。不过它写的有问题,我又改了一晚上,下面是当时记录的 prompt:

我想要一个这样的功能,你可以帮我通过 JavaScript 实现吗?是要将原始文本进行处理,得到处理文本,目标如下。

  1. 有一个隐藏词表(后称 A),对于此词表内全部单词,若在原始文本中作为单词出现(即前后都为空格),那么替换为一定长度的下划线(下划线长度统一设置,下文同。为方便行文用长度为一的下划线「_」示例)。如 abc 在 A 中,那么「abc」处理为「_」
  2. 有一个保留词表(后称 B),对词表内全部单词,保留原始文本内出现的单词。如 def 在 B 中,那么「def」处理为「def」
  3. 对于剩下的单词,如果单词长度大于 1,那么保留首字母,并加入一定长度下划线。如 ghi 既不在 A 中也不再 B 中,则「ghi」处理为「g_」
  4. 对于数字开头的内容,一律保留。如「60」处理为「60」,「60km」处理为「60km」
  5. 以「,」「.」等标点符号结尾的,将除标点外内容按上面内容处理,标点保留。如「abc.」处理为「_.」,「60km,」处理为「60km,」
  6. 对于斜杠分割的内容,将斜杠分割的多个部分视为多个单词依照上面内容进行处理。如「abc/def/60km/ghi.」处理为「_/def/60km/g_.」
  7. 有括号的内容,将括号内的内容按上面规则进行处理,并保留括号。如「(abc/def/60km/ghi. abc def ghi 60km abc. ghi, def)」处理为「(_/def/60km/g_. _ def g_ 60km _. g_, def)」
  8. 处理文本前如果有指令符「:」(注意,指令符至多只能有一个出现),则根据文本内容的「_」进行分割,不再遵循上面的内容,同时结果不包括指令符。如「:_(abc/def_/60km/_ghi. abc def ghi) 60k_m ab_c. gh_i, def」处理为「_/60km/_m ab_i, def」
  9. 处理文本前如果有指令符「-」,则忽略隐藏词表与保留词表,按照目标 3 进行处理(但是目标 4~7 还是要满足),同时结果不包括指令符。如「-(abc/def/60km/ghi. abc def ghi) 60km abc. ghi, def」处理为「-(a_/d_/60km/g_. a_ d_ g_) 60km a_. g_, d_」
  10. 处理文本前如果有指令符「#」,则将整个内容替换为设定下划线长度的两倍,同时结果不包括指令符。如「#(abc/def/60km/ghi. abc def ghi) 60km abc. ghi, def」处理为「__」

想到一个很搞笑的事情,当时 New Bing 浑身都是敏感点,稍微一点写不好它就罢工不干。例如说我一开始是「要求」,它就拒绝给我回答,我后面改成了「目标」才行,真矫情当时。说实话我看了一下这个 prompt,感觉比我现在写的好,能想到这么多边界条件。不过这也是从常久的痛点出发了,自然可以覆盖比较多实用的情况。

通过搜索可以看一看我指令符用在了什么地方。

例如说 : 指令符就是最自由灵活的了,可以任意操作。如 :p_lay_ _a trick/tricks_ _on_ 挖空为 p__ __ __,将 a trick 与 tricks 视为了一体。除此以外还有其他用例,如想要保留两个开头啊之类的。

- 指令符用来全部挖空。例如说一些简单的专有名词,不需要开头一个字母提示,虽然说可以用 : 指令符然后 _ 自己添加,但总归太麻烦了。例如 -billion 挖空为 __,这个如果提示 b__ 就没意义了,因为考的就是这个 b。

# 操作符没找到使用,不过场景大概想的就是不提供任何提示,只是这种可能给 - 实际上取代了。

说起来看了看代码我发现还有一个指令符 !,看起来更像是原 prompt 的 - 指令符。只有一个使用的地方:!other than,挖空为 o__ t__,默认情况下这两个词都会完全隐藏(因为都在隐藏词表中)。

这个词表也不是一成不变的。实际上很多都是我背的途中,注意到一些特殊的词可以被全部隐藏,往往会积累一点,然后回家后一起加入词表。

然后还有普通的挖空 Cloze。首先是我有一个 Cloze.ahk,用来方便地进行挖空。因为 Anki 没有提供在 HTML 编辑器中,使用快捷键对所选内容进行挖空的功能,我就自己实现了一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#Requires AutoHotkey v1.1+

; Anki Cloze
;;; 特性   Features
;;;;; 1. 自动清除首尾空白字符                         Automatically clean left and right empty characters
;;;;; 2. 自动添加尾部空格                             Automatically add right space
;;;;; 3. 末尾字符是 '}' 时自动添加空格                Automatically add space if the right character is '}'
;;;;; 4. 自动将 '}}' 改为 '} }'                       Automatically change '}}' to '} }'
;;;;; 5. 未选中内容输入空白 Cloze 并移动光标至其中    Create empty Cloze and put cursor into it while selecting nothing
;;; 有待支持特性 Unsupported Features
;;;;; 1. 多光标支持                                   Multiple cursors supprot
;;; Anki 捷径   Anki Shortcuts
;;;;; Shift + F1            重置 Cloze 序数           Reset Cloze order
;;;;; F5                    保持 Cloze 序数           Keeping Cloze order
;;;;; F6                    递增 Cloze 序数           Growing Cloze order
;;;;; `                     HTML 换行                 HTML Linebreak
;;;;; Shift + `             保存卡片并清除序数        Save card and Reset Cloze order

Cloze(keep := 0) {
    clip := ClipboardAll
    Clipboard := ""
    SendInput {Ctrl Down}x{Ctrl Up}
    ClipWait 0
    Text := Trim(Clipboard, " `t`r`n")
    Clipboard := clip
    (!(keep and turn)) ? turn ++
    if (Text = "") {
        SendInput % "{Text}{{c" turn "::}}"
        SendInput {Left 2}
    }
    else if (SubStr(Text, 1, 3) = "{{c" and SubStr(Text, -1, 2) = "}}" and InStr(Text, "::") > 4) {
        (!(keep and turn)) ? turn --
        SendInput % "{Text}" SubStr(Text, InStr(Text, "::") + 2, StrLen(Text) - 8)
    }
    else
        SendInput % "{Text}{{c" turn "::" StrReplace(StrReplace(Text, "}}", "} }"), "}}", "} }") ((SubStr(Text, 0) = "}") ? " }}" : "}}")
}

Global turn := 0

#IfWinActive ahk_exe anki.exe

+f1::turn := 0
f1::Cloze(1)
f6::Cloze()
`::SendInput {Text}<br>`n

#InputLevel 1

+`::SendInput {Ctrl Down}{Enter}{Ctrl Up}{Shift Down}{F1}{Shift Up}

这个其实现在还在用,不过已经用 v2 语法重写,同时修改了很多其他地方了。

此外因为 Cloze 有提示特性,我也给它加了点特性。首先我 Cloze 基本上[14]都换成了下划线,更符合中国宝宝体质,替换方法是网上找到的,现在还在用:

1
2
3
4
5
[].forEach.call(document.querySelectorAll('.cloze'),
    function(V0) {
         V0.innerHTML = V0.innerHTML.replace(/\[...]/g,"  ");
    }
);

例如说有动词短语让我写,考点是介词,可以这样:{{c1::arm/equip sth. with sth.::@auto_phrase}} 用…装备,会挖空成:

我看了一下,其实还有 @auto_spell 命令,但没用过,代码似乎是直接从上面复制来的。而即便是 @auto_phrase 命令,硬要说其实也就用了「一次」,都是在 2023.4.16 这一天用的,制了 429 张卡,看时间的话似乎是批量导入的?都是 17:15。

还有其他的,例如说语文还有默写卡片,其他还有选择题模板(网上找后改了点)等,不再详细介绍了。

其实这个默写卡片设计不是很好,字的显示应该放在下面,这样翻面的时候只有字会显示,而句子不会移动,视觉观感更好。

其实看了一下,这些特殊的卡片很多都是一时的产物,在诞生后集中使用一段时间后就再无使用了。

填空题模板,改自 Anki选择题模板–极好用好看的选择题模板(单选+多选) - 知乎,不过也用得很少。

真正的常青树——直到现在还在使用,并迭代更新——还得是最为基础的 Basic 与 Cloze 卡片,最灵活,也最适用各种情形。

LaTeX

暂且先将目光从 Anki 拉回,我刚刚在讲什么?原来是在翻阅 QQ 空间的历史。翻到了 2021.1.23 那一天,回想起来我那时候对我的学习系统的构想与憧憬。于是我敲下来 Anki 的第一部分。

不到一周后的又一条说说,又将我引入了 LaTeX\LaTeX 的世界。

首先问一个很简单的问题:Markdown, LaTeX\LaTeX, KaTeX\KaTeX, MathJax 这四个东西究竟是什么?有何区别?

对于现在的我来说,这再简单不过了。Markdown 是一个轻量的标记语言,可以转换成 HTML;而 LaTeX\LaTeX 是一个排版语言。

那为什么经常会看到有 Markdown 中的 LaTeX\LaTeX 这种说法呢?这里的 LaTeX\LaTeX 指的其实是 LaTeX\LaTeX 排版的数学公式。标准 Markdown 中其实并不包含数学公式这一项,但是是作为一个常见的拓展,而 KaTeX\KaTeX 与 MathJax 就是实现在 Markdown,甚至一般的 HTML 等处使用 LaTeX\LaTeX 公式的方式。

由于 HTML 与 LaTeX\LaTeX 有着天翻地覆的区别,因此想要完完整整复刻 LaTeX\LaTeX 的所有内容是不可能的,这两个实现都只支持 LaTeX\LaTeX 公式的子集。其中 MathJax 支持的命令会多一点,不过 KaTeX\KaTeX 的性能更好。

我个人基本上用的都是 KaTeX\KaTeX。也不知道是不是心理作用,看多了的话可以一眼分辨出来 KaTeX\KaTeX 与 MathJax,我觉得前者更好看,更贴近 LaTeX\LaTeX

不过最开始的我是并不知道这些的。随着时间的流逝,我对 MathType 有了诸多不满,主要可能是比较丑陋。

而我从朋友那里了解到了一个名词,Markdown,以及 Typora,于是我回家后就试了一下。

1.28,我花了好大的功夫,敲了一晚上,输入了下面的内容:

$\text{If}\ a_{i}\in\mathbb{N}_{+}; n>1;i=1,2,\cdots,n;\sum\limits_{i=1}^{n}a_{i}=\prod\limits_{i=1}^{n}a_{i}\\\text{Question:}\ 1.\text{If}\ a_{i}\ \text{does not equal to each other,prove that there is no solution when }n\ne3.\\\qquad\quad\quad\,\,\,2.\text{Prove that there is at least }(n-3)\ 1\text{ when }n>3.\\\text{Proof:}\\\text{First,}n=2:\\\text{The question equals that }a+b=ab\Rightarrow\cfrac{1}{a}+\cfrac{1}{b}=1\\\text{Obviously,} \{2,2\}\text{ is a set of solution.Are there more solutions?}\\\text{If }a=1 \text{ or }b=1\text{ ,it can't.So }a\ne1\and b\ne1.\\\text{If }a>2,b=\cfrac{1}{1-\cfrac{1}{a}}<2,\text{but }b\ne1\text{,So there are no more solutions.}\\\text{Then }n>3:\\\text{The question equals that }\cfrac{1}{a_{2}a_{3}\cdots a_{n}}+\cfrac{1}{a_{1}a_{3}\cdots a_{n}}+\cdots+\cfrac{1}{a_{1}a_{2}\cdots a_{n-1}}=1\\\text{Might as well set }a_{1}\ge a_{2}\ge\cdots\ge a_{n}\\\text{If }a_{n}>1,\text{so the minimum of }a_{n}=2,\text{therefore }a_{1}\ge a_{2}\ge\cdots\ge a_{n}\ge2\\\text{which makes that }\cfrac{1}{a_{2}a_{3}\cdots a_{n}}+\cfrac{1}{a_{1}a_{3}\cdots a_{n}}+\cdots+\cfrac{1}{a_{1}a_{2}\cdots a_{n-1}}\le\cfrac{1}{2a_{2}a_{3}\cdots a_{n-1}}+\cfrac{1}{2a_{1}a_{3}\cdots a_{n-1}}+\cdots+\cfrac{1}{a_{1}a_{2}\cdots a_{n-1}}\le\cfrac{n}{2^{n-1}}\\\text{Easy to see when }n=2,n=2^{n-1},\text{and }n^\prime=1,\left(2^{n-1}\right)^\prime=2^{n-1}\ln2\\\text{When }n=2,2\ln2=\ln4>1.\text{So when }n>2,2^{n-1}>n,\text{equals that }\cfrac{n}{2^{n-1}}<1\\\text{So }\cfrac{1}{a_{2}a_{3}\cdots a_{n}}+\cfrac{1}{a_{1}a_{3}\cdots a_{n}}+\cdots+\cfrac{1}{a_{1}a_{2}\cdots a_{n-1}}<1,\text{contradictory!So }a_{n}\le 1\Rightarrow a_{n}=1\\\text{So }\cfrac{1}{a_{2}a_{3}\cdots a_{n}}+\cfrac{1}{a_{1}a_{3}\cdots a_{n}}+\cdots+\cfrac{1}{a_{1}a_{2}\cdots a_{n-1}}=1\\\text{If }a_{n-1}>1,\text{therefore }a_{1}\ge a_{2}\ge\cdots\ge a_{n-1}\ge2\\\text{which makes that }\cfrac{1}{a_{2}a_{3}\cdots a_{n-1}}+\cfrac{1}{a_{1}a_{3}\cdots a_{n-1}}+\cdots+\cfrac{1}{a_{1}a_{2}\cdots a_{n-1}}\le\cfrac{1}{2a_{2}a_{3}\cdots a_{n-2}}+\cfrac{1}{2a_{1}a_{3}\cdots a_{n-2}}+\cdots+\cfrac{1}{a_{1}a_{2}\cdots a_{n-2}}\le\cfrac{2n-1}{2^{n-1}}\\\text{When }n=3,2n-1=5,2^{n-1}=4\text{. When }n=4,2n-1=7,2^{n-1}=8,\text{so a solution of }\cfrac{2n-1}{2^{n-1}}=1\text{ is in }(3,4),\text{ and}(2n-1)^\prime=2,\left(2^{n-1}\right)^\prime=2^{n-1}\ln2.\\\text{ When }n=4,2^{n-1}\ln2=8\ln2>2,\text{ so when }n\ne 4,2^{n-1}>2n-1,\text{ that is }\cfrac{2n-1}{2^{n-1}}<1\\\text{So If }a_{n}>1,\text{so the minimum of }a_{n}=2,\text{therefore }a_{1}\ge a_{2}\ge\cdots\ge a_{n}\ge2\\\text{which makes that }\cfrac{1}{a_{2}a_{3}\cdots a_{n}}+\cfrac{1}{a_{1}a_{3}\cdots a_{n}}+\cdots+\cfrac{1}{a_{1}a_{2}\cdots a_{n-1}}\le\cfrac{1}{2a_{2}a_{3}\cdots a_{n-1}}+\cfrac{1}{2a_{1}a_{3}\cdots a_{n-1}}+\cdots+\cfrac{1}{a_{1}a_{2}\cdots a_{n-1}}\le\cfrac{n}{2^{n-1}}<1\\\text{contradictory!}\text{ So }a_{n-1}\le1\Rightarrow a_{n-1}=1\\\text{So }a_{n}=a_{n-1}=1,\text{ contradictory!}\\\text{In summary,there is no set of solution that the element in the set doesn't equal to each other when }n\ne3.\\\color{VioletRed}{\mathcal{Thanks\ for\ reading\ my\ broken\ English.Just\ a\ try\ to\ learn\ Latex!}}$

我就不放在这真实渲染了,而且当时用的是 MathJax,KaTeX\KaTeX 的话要把 \and 改成 \land 才能正常显示,此外下面的颜色命令也不同等。贴一下当时的图片:

这也便是我第一次 "Latex" 尝试了。

第二次就是两天后的 1.30,依旧是在 Typora 的一个数学公式里面塞了全文。不过这一天我提及了:

……昨天在准备Texlive和Texstudio,准备弃用Typora了(哈哈,Typora用得不习惯,不过不会卸载)……

现在看到有点心梗,应该这样写才对,是不是看起来更舒服点?

昨天在准备 TeX Live 和 TeXstudio,准备弃用 Typora 了(哈哈,Typora 用得不习惯,不过不会卸载)

摸索中前进:
\date{\today}本来是1月31日的,结果昨天摸鱼没做完变成2月1日了。
过程艰难,期间出现许多的困难:例如目录不显示了(就在我打这段文字前一分钟发现加载上来的图片没有目录,又去编译了一遍);页眉出现很丑的Section了;eqref变??了···诸如此类,好在最终都一一克服了。
边查资料边尝试,也学到很多:比如苦于\换行不会缩进,查到了需要用\par;学会了调节页面格式(严格来讲并没有学会,我只是复制过来了,不过好歹让我知道还有这回事儿);用了一下tikz和pgfplots(只在结语有用,可以说是为用而用了,不然我usepackage干嘛)
当然还有困惑的:直接\Latex并无法显示它漂亮的字形,我也没有查到方法,只好放弃了;还有一个最后结论那里我去学到了加粗\pmb,但是我还想加\color{red}醒目,尝试了几次也都以失败告终。
TexStudio Work I
告一段落了

然后这是 2.1 的第一篇 LaTeX\LaTeX,这样的 tsw(TeX Studio Work) 一共有 5 个,都有留存:

  1. 关于 kknn 项自然数求和公式的推导(2021.2.1)
  2. 读书笔记之探讨形如 zn=wz^n = w 的方程的解(2021.2.10)
  3. 读书笔记之解简单的微分方程(2021.2.12)
  4. 笔记之线性递推数列通项公式(2021.2.12)
  5. 摆线(2021.3.12)

2021.2.4

本来还有个解微分方程的,应该也写上来权当温习的(毕竟我只有坐公交等公交的碎片时间才能看),但因为我要摸鱼了(理直气壮),就懒得写了,就先这样。顺便说一下,z好丑啊要不是还有一种美感我都怀疑我算错了。
改好了
2.10修改更新

看的书就是《普林斯顿微积分读本》,那时候正好在公交上看。另外根据相册(说说已经删掉了),同期看的书还有《明朝那些事儿》。

查了一下,2021 的除夕、春节在 2.11 与 2.12,我也记得在晚上的时候,家里人似乎坐一起看电视唠嗑?我在旁边一个键一个键地手敲 LaTeX\LaTeX 命令。

在此之后,我大概就逐步了解了什么是 Markdown,就基本再没直接碰过 LaTeX\LaTeX 了。也就是说,其实我是先知道了 LaTeX\LaTeX,再学的 Markdown。但 LaTeX\LaTeX 学还是学了,不至于一头雾水了,因此在大学的时候也可以直接用来做作业。

Markdown

虽然后面我逐渐弄懂了 Markdown 与 LaTeX\LaTeX 的区别,也知道了 Typora 其实是一个 Markdown 编辑器,$...$LaTeX\LaTeX 数学公式语法的分界符等,但我后面用的 Markdown 编辑器却不是 Typora,而是——VS Code。

4.22 的时候,我注意到了绝对值函数的拟合效果,并进行了一点研究。似乎当时是题目有多个绝对值函数在一起,然后我发现这几段加在一起显示的效果居然有点像二次函数。因此我进行了推导,并证明了我的猜想。由此在 5.9 写出了我的第一篇 Markdown 笔记。

可以看到这时候还非常稚嫩。首先标题标记 # 后面没有空格,不符合语法要求,只是因为解析器比较宽容容忍了这种写法;其次,滥用标题,例如下面的推广,拿标题来起强调效果,这个其实能推测一下我是怎么想的,普通加粗不奏效,因为加粗对数学公式是无效的,但标题的样式改了颜色,这个倒有效果,因此我就用标题代替了;至于说排版的「盘古之白」什么的,倒是小问题了。

也正因此这篇笔记我没有改一下格式上传为博文,还是太嫩了些。

笔记系统

从文件的元数据可以推算,我大概在 21 年 9 月初开始建立了我的笔记系统。在此之前有大概九篇笔记,从 2021-06-20 开始,大约每周或每两周一篇的内容,一直到 2021-08-25。

现在这里简单提一下我当时的笔记系统构想。不过因为最终发展跟我最初的构思有很大差异,同时我并没有以任何形式记录下我内心的想法,因此其实没法正确复原当时真正的希望。甚至当时其实还取了名字的。

简单来说就是电子笔记 + Anki,有点像我现在大学的形式。电子笔记主要是易修改、检索,美观性也更好,而 Anki 在当时被我视为了「救世主」,用来让我将笔记拆分成知识点,从而可以在日常的刷卡中记忆下来的。

因此笔记其实也不是严格的笔记,会为了方便制卡而进行刻意的「迎合」。

因为卡片知识点是一条一条的,我也会自然而然往那个方向发展,例如说中期的局部内容(Week-01_21-09-03):

1
2
3
4
5
6
1. 人体内含有大量<u>以水为基础</u>的液体,这些液体统称为**体液**

2. 体液中含有大量的水、离子和化合物。

3. 体液组成示意图
![](images/body-fluid.png)

这样就方便记录。但是我逐渐感觉这不够好,这强制要求了使用有序列表,不方便复制,从而也让在其中使用有序列表变得困难。

那为什么不直接用 --- 呢?因为我笔记内部自己就有可能出现。而且其实我当时不仅有考虑 Anki,还有想过整合效果的[15],要是用 --- 都是分隔线,很容易混淆。

而后期用自定义 class 解决了,同时加上了快捷键支持(2022-07-31):

1
2
3
4
5
6
7
8
9
10
11
****是良好的*半导体材料*,可用于制作<u>芯片</u>

<hr class='section'>

**二氧化硅**具有良好的*折光率*,可用于制作<u>光导纤维</u>

<hr class='section'>

*高分子材料*都是**混合物**。

例如<u>淀粉</u><u>纤维素</u><u>蛋白质</u>等。

那大学是怎么考虑的呢?大学路径不一样。首先我高中的知识、笔记都是以实体呈现在课本或是纸质笔记中的,我是「转录」成电子笔记,而大学笔记本身的形式就是电子笔记,二者形式就不同,前者与其说是笔记,更不如说是知识点的聚合体。

因此大学的时候制卡,我其实不能像高中一样一条条全部复制粘贴,而是需要自己从笔记中挑选出我认为算是知识点的部分来制卡。

笔记的作图部分不是这部分的重点,而且笔记系统要是详细展开的话,完全可以另外成篇,反倒是有点喧宾夺主了。之前提过一部分(往下滑一点),如 KingDraw, Draw.io, Mathematica, Inkscape 等,以及最主要的 GeoGebra 的发展史,都有一点涉及。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Notes Catalogue
<div style="text-align:right;color:white;font-size:18px"><b>——笔记目录</b></div>

[toc]

<!-- 2021-06-20.md -->

@import "2021-06-20.md" {line_begin=0 line_end=3}

@import "2021-06-20.md" {line_begin=6}

---

<!-- 2021-06-26.md -->

@import "2021-06-26.md" {line_begin=0 line_end=3}

@import "2021-06-26.md" {line_begin=6}

...

当时构建笔记系统给每学期的内容加了一个 Notes-Catalogue.md,用来整合一学期的全部内容到一个文件中。可以看到用的是 MPE(Markdown Preview Enhanced) 的扩展语法,没有什么可移植性。然后可以导出成一个 HTML 文件,现在还在。上面的是高一下的,15M,最多的是高二上,170M,再然后就没有搞了。

排版

翻阅旧笔记的过程中我发现一个很有意思的现象。

8.25-8.27 的三天,是高二暑假前夕,我将荒废了好一阵子的笔记系统重新捡了起来,重新开始写笔记。我选择的目标是物理选择性必修三,拿来预习(也正是那段时间用 Inkscape 画了点仿图)。但这段时间我的笔记还没有在中英文等中插入空白:

2021-08-25

电荷的多少叫作电荷量,用QQ 表示,有时也可以用qq 表示。

电荷量的单位是库伦,简称,符号是C\mathrm{C}

但是一周后下学期的第一篇笔记,有了:

Week-01_21-09-03

  • 语文《论语》十二章课下注释 + 三二预习部分 Anki 制卡
  • 英语疑难破4 Unit1 Anki 制卡
  • 笔记 Anki 制卡

……

  1. 血浆中 90%90\% 为水;其余 10%10\% 分别是:蛋白质( 7%7\% ~ 10%10\% ),无机盐(约 1%1\% ),以及血液运输的其他物质,包括各种营养物质(如葡萄糖)、激素、各种代谢废物等。

……

  1. 反应速率 vv (取正值)常用单位有 mol/(Ls)\pu{mol/(L.s)}molL1s1\pu{mol.L-1.s-1} 等。

甚至更过头了,如全角标点周围也加上了。猜想这可能跟当时的 Snippets 有关。但从此我就有了这样的自觉了,因为开头的任务中也是这样的,这是无关 Snippets 的,纯粹是认识到了这件事情才会这样做。

我再继续翻了一下,整个高二上,也是高中电子笔记鼎盛时期,都是全空格的,即公式周围不管是标点还是汉字,都是会有空格的,一直到最后一周的记录 Week-21_22-01-17,也就是 22 年初。

转机在 Week-06_22-03-28 与 Week-16_22-06-11 之间,在上一篇中还是如此,但下一篇就改了。当然中间为了什么隔了十周没笔记呢,哈哈……

不过从 Snippets 相关的 commit 记录可以知悉,起码在初始提交的 2022.5.21 我就已经转变了习惯,因为有了标点相关的变量:

1
2
# https://github.com/pilgrimlyieu/Snippets-Dependencies/commit/a6f7e6d2c772e5530898a9fb488b115f3f377c6a
before_puncs = ['', ' ', '', '', '', '', '\'', '', '"', '[', ']','(', ')', '{', '}', '<', '>', '', '-']
1
2
3
4
context "text()"
snippet '(\W)lm|^lm' "行内公式 Inline Formula" rA
`!p before = match.group(1) or ''; snip.rv = before + ('' if before in before_puncs else ' ')`$$1$`!p snip.rv = '' if t[2][:1] in after_puncs else ' '`$2
endsnippet

总而言之这时候我应该就开始觉醒了一些有关排版的「强迫症」。随后先射箭后找靶,在 22 年 8.3 给自己找到一个理论依据:Requirements for Chinese Text Layout - 中文排版需求

之所以说是后找靶,是因为虽然我阅读了一番,但并不是严格按照里面的要求来,其实是按照我自己的审美来。即便是同一种情况,根据时间的不同,我有时也会做出不同的排版选择。

类似的还有经典的 i,e,π, ⁣d\i, \e, \pi, \d,作为虚数单位、常量自然对数的底数以及微分符号,我使用了正体;但同样是常量的 π\pi,依旧是斜体,因为我觉得斜体更好看、更符合我这十多年来的见闻与习惯,以及 KaTeX\KaTeX 似乎打不出正体来。

因此从标准吗?首先你得说什么「标准」?你这标准自己认是标准吗?然后再说是,如从。

HyperSnips

源起

我还记得我正则表达式最开始就是在 VS Code 中学的。

高一下的笔记中有几个笔记命名为 BioRegexTest*.md,共四篇。

第一篇我记录的是 2021.8.23,内部注记「通过正则表达式替换成下面内容」。说明这时候我就已经掌握基础的正则表达式,并可以使用 VS Code 的替换操作进行一些便捷操作了。

按照我的记忆,当时就是为了进行搜索替换,而发现了「正则表达式」,然后在菜鸟教程学习了一番。而在后面很多正则表达式的测试,则是在 regex101: build, test, and debug regex 中进行的。

我之前的说法

2024.8.21

试问用 Vim, UltiSnips 和 LaTeX\LaTeX 做笔记的人,谁不识 Castel?又有谁没看过他的惊世名篇?Castel 作为我重要的引路人,以后有机会肯定是会写篇博文聊聊的。

这篇也算是兑现了我之前的承诺吧,同时也是表达一下我对 Castel 的敬仰与哀悼之情。

不过最早我并不是看他的原博文,而是看的知乎转载的文章,是量子位还是机器之心来着?

虽然我最早看这篇文章的时间具体已不可考,但根据活动记录可以推知在 2021.8.11 之前,因为我在这时候在 GitHub 留下了相关痕迹。

具体的震撼我就不过多阐述了。回看我上面数学公式的书写记录,从一开始的截图文本功能,到后面的几何画板,再到 MathType,最后走上了「正道」LaTeX\LaTeX。唯一制约我的就是输入效率了。

因此在看到这篇文章后,我为之一震,这就是我要找的《葵花宝典》!大约自此,我将笔记系统与 Anki 串联起来,构想了一套自己的学习系统。

不过里面用的是 Vim,但我当时已经在打磨 VS Code 了,已经被束缚住了,无法挣脱。怎么办呢?

一开始其实有一番战 Vim,结果灰溜溜地逃跑了。Vim 对于新手来说确实是太抽象了,当时 Windows gVim 也是非常丑陋、辣眼睛,加上操作离人太远、离神太近,我自然很快就败退了。

不过后面我就了解到,里面用到的 Vim 插件 UltiSnips,在 VS Code 中有仿制的 HyperSnips,只不过后者用的是 JavaScript,而不是 Python。

加上 Anki,我就被迫学了点 JavaScript,当时是在《现代 JavaScript 教程》中学的,也是我第二个「会」的编程语言(排除了 DSL AutoHotkey),一直到我大一学 C 语言,再到后面学 Java, C++, Rust……

然后就开始了我相当一段时间的写 Snippets 时光,写了很多可以说是「奇技淫巧」的 Snippets。大约也就是从这时候起,我的兴趣就从数学,或者说这时候已经是数学公式了,转移到了「写」数学公式上。

找了一下,我现在还留着最初的 2659 行的 markdown.hsnips,我重读一下,然后摘录一点。

1
2
3
4
5
##该版 "markdown.hsnips" 最新适用于 HyperSnips v0.2.3
##若需要使用该版 snippets,应安装不超过 v0.2.3 的版本
##主要 BUG 是新版 HyperSnips 取消了对 "`` ... ``" 内字符串通过 "$n" 或 "${n:text}" 来制作 TabStops 的功能
##取而代之的是用 "snip.tabstop(n)" 或 "snip.tabstop(n, 'text')"
##有时间了再进行更新

嗯,依旧是「有时间」。

括号变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
function bra(string, left, right, option, content){
    if (content == 0){
        var addition1 = "";
        var addition2 = "";
    }
    else if (content == 1){
        var addition1 = "${1:";
        var addition2 = "}";
    };

    if (option == 1){
        let str = string;

            let depth = 1;
            let i = str.length - 1;

            while (true) {
                    if (left == right && left == "|" && str.slice(i - 5, i) != "\\left" && str.slice(i - 6, i) != "\\right"){
                        if (i == -1 || str[i] == left) break;
                        i -= 1;
                    }
                    else {
                        if (str[i] == right && str.slice(i - 5, i) != "\\left" && str.slice(i - 6, i) != "\\right") depth += 1;
                        if (str[i] == left && str.slice(i - 5, i) != "\\left" && str.slice(i - 6, i) != "\\right") depth -= 1;
                        if (depth == 0 || i == -1) break;
                        i -= 1;
                    }
        }

        if (str.slice(i + 1) == ""){
            var result = "$1";
        }
        else {
            var result = str.slice(i + 1);
        };

        let results = str.slice(0, i) + "\\left" + left + addition1 + result.replace(/\}/g, '\\}') + addition2 + "\\right" + right + "$0";
        return results.replace(/\\\\/g, '\\\\\\\\')
    }
    else if (option == 2){
        let str = string;
            
            let depth = 1;
            let i = str.length - 1;

            while (true) {
                    if (str[i] == right && str[i - 1] == "\\" && str.slice(i - 6, i - 1) != "\\left" && str.slice(i - 7 , i - 1) != "\\right") depth += 1;
                    if (str[i] == left && str[i - 1] == "\\") depth -= 1;
                    if (depth == 0 || i == -1) break;
                    i -= 1;
        }

        if (str.slice(i+1) == ""){
            var result = "$1";
        }
        else {
            var result = str.slice(i + 1);
        };

        let results = str.slice(0, i) + "\\left\\" + left + addition1 + result.replace(/\}/g, '\\}') + addition2 + "\\right\\\\" + right + "$0";
        return results.replace(/\\\\(?!left|right|\}|\{)/g, '\\\\\\\\')
    }
}

这是当时手搓的一个加括号的函数,我勒个去,真是不堪入目,野路子出家是这样的。使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
##分隔符号
# 支持小括号、中括号、转义大括号、绝对值符号的升级功能(为了稳定,不支持普通大括号的转义升级)
context math(context)
snippet `(^.*)(?<!\\right)(\)|\]|\\\}|\|)\.` "括号变形" Ai
``
let first = ["(", "[", "\\{", "|"];
let second = [")", "]", "\\}", "|"];

let string = m[1];
if (/\$/g.test(string) == true){
    string = string.replace(/\$/g, "")
}
if (m[2] == "\\}"){
    var left = "{";
    var right = "}";
    var option = 2;
}
else {
    var left = first[second.indexOf(m[2])];
    var right = m[2];
    var option = 1;
}

rv = bra(string, left, right, option, 0).replace(//g, "\\$")
``
endsnippet

这是一个为普通括号「升级」的 snippet,将普通的括号升级为自动调整大小的括号。从正则匹配可以看出来,匹配的内容首先是从开头的任意字符,一直到右括号,且前面不含 \right。而最后是一个 . 作为「触发符」,因为这样广泛的匹配我可能有很多用途,只靠这些然后 Tab 感觉不太「灵活」,因此我当时选择了一部分不同的「触发符」,并开启了自动触发,这样输入了 (a) 后再按一个 . 就会自动变成 \left(a\right),跟按 Tab 是一样的。

而之所以要从开头全匹配,也是因为正则表达式无法做到括号匹配这件事情。我在刚开始的「正则狂热」阶段——即尝试将所有的匹配逻辑靠正则实现——时并没有意识到这件事,是在我慢慢摸索,写了很多正则后逐渐意识到的。

注音记号(Accent)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
context math(context)
snippet `((?<![a-zA-Z])\w|\\[\w]+)_([\dijn]|\{.\})(bar|hat|vec)` "下标均值/帽/向量" Ai
``
rv = "\\" + m[3] + "{" + m[1] + "}_" + m[2]
``
endsnippet

context math(context)
snippet `hbar` "hbar" Ai
\hbar
endsnippet

context math(context)
snippet `(?<!\\|[a-zA-Z])(i|j)(bar|hat|vec)` "i/j均值/帽/向量" Aim
``
rv = "\\" + m[2] + "{\\" + m[1] + "math}"
``
endsnippet

context math(context)
snippet `((?<!\w)\w|\\\w+)(bar|vec)` "均值/向量" Ai
``
rv = "\\" + m[2] + "{" + m[1] + "}"
``
endsnippet

context math(context)
snippet `(\\?\w*)(bar|hat|vec)` "均值/帽/向量" Aim
``
let origin = ["bar", "vec", "hat"]
let turn = ["overline", "overrightarrow", "hat"]

var str = Turn(origin, turn, m[2])

let result = "\\" + str + "{";

if (m[1] == ""){
    result += "$1"
}
else {
    result += m[1]
};

result += "}";

if (m[1] == ""){
    result += "$0"
}
else {
    result += ""
};

rv = result;
``
endsnippet

这是现在仍然会使用到的 snippets 了。常常会遇到要写 bar, hat, vec 的情形(主要是 vec),但如果你是先用 snippets 造出来 \vec{␣}[16],你还得再按一次跳出来,有点麻烦。此外跟手写的流程也不一样,我手写的时候往往是写好里面的符号再画上箭头等,改变了逻辑有点不舒服。

于是我就造了上面的「均值/向量」snippet,可以直接 avec 变成 \vec{a},这样就畅快多了。除了单字母外还包括了 \\[\w]+ 也就是 \\\w+ 这种情形,对于希腊字母非常常见,如 \alphavec 变成了 \vec{\alpha}

但是使用过程中还遇到一个情况,有下标怎么办呢,例如说 a_i,如果还用那个,就会变成 a_\vec{i},显然在比较常见的情况并不是我想要的,因此又有了「下标均值/帽/向量」snippet,不过单一下标的情况仅仅放行了几种常见的下标 i, j, n 与单数字,这样之后就可以 a_ivec 变成 \vec{a}_i 了。

咦,怎么不是 \vec{a_i} 呢?这是因为经过我的观察,下标与上标放在 Accent 外面比较合适,可以观察一下 ai\vec{a}_iai\vec{a_i} 之间的区别。如果是上标的话,差异会更显著:a2\vec{a}^2a2\vec{a^2}

还有一些额外的处理,如 \bar{h} hˉ\bar{h}\hbar \hbar\vec{i} i\vec{i}\vec{\imath} ı\vec{\imath} 等,这里就不赘述了。

这还不够。高中数学中常见的要标箭头的,还有例如 AB\overrightarrow{AB} 的情形,如果用 \vec 就会是 AB\vec{AB},显然不是我想要的。因此最下面的「均值/帽/向量」就对这种情形进行了处理。不过用了一个不明所以的 Turn 而不是搞个字典我有点摸不着头脑。

这些 snippets 一直传承下来,我后面改用 UltiSnips 时也进行了改写并迭代:

1
2
3
4
5
6
7
8
9
10
11
12
13
special_bar_hat_vec = ['i', 'j']
special_hbar        = ['\\bar{h}', '\\hbar']
map_bar_hat_vec     = {'bar': '\\overline', 'hat': '\\widehat', 'vec': '\\overrightarrow'}

bars = ['\\bar', '\\overline']
hats = ['\\hat', '\\widehat']
vecs = ['\\vec', '\\overrightarrow']

def bar_hat_vec(target, word, subscript = ''):
    return '\\' + target + '{' + ('\\' + word + 'math' if word in special_bar_hat_vec else word) + '}' + (subscript or '')

def long_bar_hat_vec(target, word, subscript = ''):
    return map_bar_hat_vec[target] + '{' + word + '}' + (subscript or '')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
context "math()"
snippet "\\?(vec|bar|hat)" "拔/帽/向量 Bar/Hat/Vector" wr
\\`!p snip.rv = match.group(1) + "{"`${1:${VISUAL}} }$0
endsnippet

context "math()"
snippet "(\\bar{h}|\\hbar)\\" "hbar" ir
`!p snip.rv = choose_next(match.group(1), special_hbar, 2)`
endsnippet

context "math()"
snippet "(\b[a-zA-Z0]|\\[a-zA-Z]+)([_^](?:\{\S+\s?\}|[\da-zA-Z]))?(bar|hat|vec)" "拔/帽/向量 Bar/Hat/Vector" Ar
`!p snip.rv = bar_hat_vec(match.group(3), match.group(1), match.group(2))`
endsnippet

context "math()"
snippet "((\\(bar|overline|hat|widehat|vec|overrightarrow))\{(.*)\})" "拔/帽/向量 Bar/Hat/Vector" r
`!p snip.rv = command_swap(match.group(1), *(bars if match.group(2) in bars else hats if match.group(2) in hats else vecs))`
endsnippet

priority -1
context "math()"
snippet "(?<!\\)\b(\w{2,}?)([_^](?:\{\S+\s?\}|[\da-zA-Z]))?(bar|hat|vec)" "长拔/帽/向量 Long Bar/Hat/Vector" Ar
`!p snip.rv = long_bar_hat_vec(match.group(3), match.group(1), match.group(2))`
endsnippet

对比来看可以注意到现在的 snippets 比最初更克制地使用了 A 选项,即自动展开。我记得当初钻研的时候,好像看到有说法称自动展开会造成一定的性能问题。

此外也可以注意到 HyperSnips 版本仅考虑了几个下标,而 UltiSnips 版本最后同时考虑了上下标,以及简单的复杂上下标(即那块 \{\S+\s?\})。

当然,还有一些「花活」,具体内容在注释中介绍了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
priority 1
# 为避免冲突,仅支持包含简单下标的变形
# Only support simple subscript to avoid comflict
# √: \bar{a}_a              --> \overline{a}_a
# √: \bar{a}_{\alpha}       --> \overline{a}_{\alpha}
# ×: \bar{a}_{\dfrac{1}{2}} --> \overline{a}_{\dfrac{1}{2}}
context "math()"
snippet "(\\bar|\\overline)(\{[\\a-zA-Z]+\s?\})((?:[_^](?:[\da-zA-Z]|\{[\\\w\d\s]+\}))?)" "拔 Bar" r
`!p snip.rv = choose_next(match.group(1), bars, 2) + match.group(2) + match.group(3)`
endsnippet

context "math()"
snippet "(\\hat|\\widehat)(\{[\\a-zA-Z]+\s?\})((?:[_^](?:[\da-zA-Z]|\{[\\\w\d\s]+\}))?)" "帽 Hat" r
`!p snip.rv = choose_next(match.group(1), hats, 2) + match.group(2) + match.group(3)`
endsnippet

context "math()"
snippet "(\\vec|\\overrightarrow)(\{[\\0a-zA-Z]+\s?\})((?:[_^](?:[\da-zA-Z]|\{[\\\w\d\s]+\}))?)" "向量 Vector" r
`!p snip.rv = choose_next(match.group(1), vecs, 2) + match.group(2) + match.group(3)`
endsnippet

希腊字母

这是部分希腊字母的简写映射表,覆盖了常用的希腊字母:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# “常用”希腊字母(@形式)
context math(context)
snippet `(?<!@)@(a|b|g|d|z|e|k|i|t|l|m|n|x|u|pi|ps|ph|r|s|o|ve|vp)` "常用希腊字母" Ai
\``
if (m[1] == "a"){rv = "alpha"};
if (m[1] == "b"){rv = "beta"};
if (m[1] == "g"){rv = "gamma"};
if (m[1] == "d"){rv = "delta"};
if (m[1] == "z"){rv = "zeta"};
if (m[1] == "e"){rv = "eta"};
if (m[1] == "k"){rv = "kappa"};
if (m[1] == "i"){rv = "iota"};
if (m[1] == "t"){rv = "theta"};
if (m[1] == "l"){rv = "lambda"};
if (m[1] == "m"){rv = "mu"};
if (m[1] == "n"){rv = "nu"};
if (m[1] == "x"){rv = "xi"};
if (m[1] == "u"){rv = "upsilon"};
if (m[1] == "pi"){rv = "pi"};
if (m[1] == "ps"){rv = "psi"};
if (m[1] == "ph"){rv = "phi"};
if (m[1] == "r"){rv = "rho"};
if (m[1] == "s"){rv = "sigma"};
if (m[1] == "o"){rv = "omega"};
if (m[1] == "ve"){rv = "varepsilon"};
if (m[1] == "vp"){rv = "varphi"};
`` 
endsnippet

这样可以用简写,如 @a 就可以直接打出 \alpha 了。当然里面其实是有一些「不公」的,如 \eta η\eta 用的次数其实远少于 \varepsilon ε\varepsilon,但前者才是 e

现在则是 \a 形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
context "math()"
snippet "\\(a|b|g|d|z|ep|e|k|i|t|l|m|n|x|u|pi|ps|ph|r|s|o|ve|vp)" "常用希腊字母 Common Greek Letter" r
\\`!p
match match.group(1):
    case "a":
        snip.rv = "alpha"
    case "b":
        snip.rv = "beta"
    case "g":
        snip.rv = "gamma"
    case "d":
        snip.rv = "delta"
    case "z":
        snip.rv = "zeta"
    case "ep":
        snip.rv = "epsilon"
    case "e":
        snip.rv = "eta"
    case "k":
        snip.rv = "kappa"
    case "i":
        snip.rv = "iota"
    case "t":
        snip.rv = "theta"
    case "l":
        snip.rv = "lambda"
    case "m":
        snip.rv = "mu"
    case "n":
        snip.rv = "nu"
    case "x":
        snip.rv = "xi"
    case "u":
        snip.rv = "upsilon"
    case "pi":
        snip.rv = "pi"
    case "ps":
        snip.rv = "psi"
    case "ph":
        snip.rv = "phi"
    case "r":
        snip.rv = "rho"
    case "s":
        snip.rv = "sigma"
    case "o":
        snip.rv = "omega"
    case "ve":
        snip.rv = "varepsilon"
    case "vp":
        snip.rv = "varphi"
`
endsnippet

还有一些触发符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
context math(context)
snippet `\\([aA]lpha|[bB]eta|[gG]amma|[dD]elta|[eE]psilon|[zZ]eta|[eE]ta|[tT]heta|[iI]ota|[kK]appa|[lL]ambda|[mM]u|[nN]u|[oO]micron|[pP]i|[rR]ho|[sS]igma|[tT]au|[uU]psilon|[pP]hi|[cC]hi|[oO]mega)\s?\.` "大写希腊字母" wAm
\\``
if (/^[a-z]/.test(m[1]) == true){
    rv = m[1].slice(0, 1).toUpperCase() + m[1].slice(1)
}
else if (/^[A-Z]/.test(m[1]) == true){
    rv = m[1].slice(0, 1).toLowerCase() + m[1].slice(1)
};
``
endsnippet

context math(context)
snippet `(\\|\\var)(epsilon|theta|phi|pi|sigma|kappa)(\s|)(;|:)` "希腊斜体" iA
``
let range = ["\\","\\var"]
rv = Cycle(range, m[1], m[4]) + m[2] + m[3]
``
endsnippet

context math(context)
snippet `(\\|\\var)(Epsilon|Theta|Phi|Pi|Sigma|Kappa)(\s|)(;|:)` "希腊斜体" iA
``
let range = ["\\","\\var"]
rv = Cycle(range, m[1], m[4]) + m[2] + m[3]
``
endsnippet

可以看出来,. 触发符用来切换希腊字母的大小写,而 ;/: 触发符则是用来切换变体。

就在这附近还出现了一个 ? 触发符,这个则是用来取反的:

1
2
3
4
5
6
7
context math(context)
snippet `(\\par|\\npar)(\s|)\?` "不平行" Ai
``
let range = ["\\par", "\\npar"]
rv = Cycle(range, m[1]) + m[2]
``
endsnippet

当然这个正则写得很有问题就是了,看着有点无语……

这个在现在的版本中没有用什么触发符,而是可以手动转换:

1
2
3
4
5
priority 1
context "math()"
snippet "\\(n?)(par\s?)" "平行转换 Parallel Transformation" r
\\`!p snip.rv = choose_next(match.group(1), nornots, 2) + match.group(2)`
endsnippet

这里出现的 \par\npar 其实都是我自己定义的(macros),而不是用 \parallel \parallel,因为我非常习惯了教材的写法,就自己定义了。这在 LaTeX\LaTeX 中非常不可取,因为 LaTeX\LaTeX 中本身就有 \par 命令。

  • \par/\kern-5mu/ A/ ⁣/BA /\kern-5mu/ B
  • \npar/\kern-13mu\smallsetminus\kern-13mu/ A//BA /\kern-13mu\smallsetminus\kern-13mu/ B

参数也许是网上找到,也许是在此基础上自己微调的。\cap

现在比较类似之前的 ? 触发符的是 /,例如说下面的:

1
2
3
4
context "math()"
snippet "\\(in|ni)(\s?)/" "属于转换 In Transforamtion" Ar
\\`!p snip.rv = match.group(1)[::-1] + match.group(2)`
endsnippet

可以直接 \in / 变成 /ni,即 \in 变成 \ni。不过我再翻了一下现在的 snippets,有一部分这样的触发符没有 A,导致我根本没用过,已经遗忘了。

逻辑符号

长期受到教科书的熏陶,我「推出」在相当长一段时间用的都是 \Rightarrow \Rightarrow,例如说 ABA \Rightarrow B

不过在使用久了以后,我逐渐意识到了 LaTeX\LaTeX 中就有这样的语义,即 \implies     \implies,例如说 A    BA \implies B

只是当时依旧不习惯,因此有了下面的 snippets,用了上面介绍过的触发符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
context math(context)
snippet => "推出" Aim
\Rightarrow 
endsnippet

context math(context)
snippet `(\\Rightarrow|\\nRightarrow)(\s|)\?` "推出" Aim
``
let range = ["\\Rightarrow", "\\nRightarrow"]
rv = Cycle(range, m[1]) + m[2]
``
endsnippet

snippet `(\\rightarrow|\\Rightarrow|\\longrightarrow|\\Longrightarrow|\\to|\\implies)(\s|)(;|:)` "右箭头" Ai
``
let range = ["\\rightarrow", "\\Rightarrow", "\\longrightarrow", "\\Longrightarrow", "\\to", "\\implies"]
rv = Cycle(range, m[1], m[3]) + m[2];
``
endsnippet

还有很多「象形」snippets 就是了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
context math(context)
snippet |-> "映射" Aim
\mapsto 
endsnippet

context math(context)
snippet `((?<!\\ce\{[^\$]+)(?:<-|<>-)|(?:<-|<>-)\.)` "左箭头" Aim
\leftarrow 
endsnippet

context math(context)
snippet `((?<!\\ce\{[^\$]+)->|->\.)` "右箭头" Aim
\rightarrow 
endsnippet

context math(context)
snippet _> "右箭头" Aim
\xrightarrow[$1]{$2}$0
endsnippet

context math(context)
snippet `(_<|_<>)` "左箭头" Aim
\xleftarrow{$1}$0
endsnippet

context math(context)
snippet neg "非" Ai
\neg 
endsnippet

context math(context)
snippet EE "存在" Am
\\exist_{$1},$0
endsnippet

context math(context)
snippet AA "任意" Am
\forall_{$1},$0
endsnippet

也许可以注意到左右箭头 -> 等,要求前面不能出现 \ce。这是因为 KaTeX\KaTeX 中可以用 mhchem 写化学方程式,如 \ce{Co + 2 Fe -> CoFFee} Co+2FeCoFFee\ce{Co + 2 Fe -> CoFFee},这就不宜将 -> 转为 \rightarrow 了。

不过 UltiSnips 却不能这样写,因为 UltiSnips 用的是 Python 正则表达式,而 Python 正则表达式「不支持非定长的正向或负向后行断言」

?<!...

Matches if the current position in the string is not preceded by a match for .... This is called a negative lookbehind assertion(负向后行断言). Similar to positive lookbehind assertions, the contained pattern must only match strings of some fixed length. Patterns which start with negative lookbehind assertions may match at the beginning of the string being searched.

而上面的断言中出现了 [^$]+,这显然不是定长的,因此这个正则表达式不合规。在 regex101 中进行测试也会报错:A quantifier inside a lookbehind makes it non-fixed width.

而 JavaScript 却是支持的:Lookbehind assertion - JavaScript | MDN

从这里可以稍微领会一下正则表达式的方言差异。

还有一点就是,Python 中正则表达式的 \w 等默认是包含了 Unicode,而不仅仅是 [a-zA-Z0-9_],直接使用会有偏差,这时候需要用 (?a) 标识,参考 re — 正则表达式操作 — Python 文档

那 UltiSnips 中是怎么解决这个问题的呢?我试了一下好像没解决,直接抛弃了这个问题,毕竟上大学没再写过化学方程式了实际上有更优雅的解决方案,用的是 context,会在后面进行介绍。

数字集合

中间还有非常多的 snippets,但因为都比较简单,形式上面或多或少都介绍过了,只能直接跳了过去。

而下面则是写了非常「复杂」的代码,因此值得稍微提一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
context math(context)
snippet `zb(|p|b|v)([\.,1-9])` "括号" Ai
``
if (m[2] == "."){
    var num = 1
}
else if (m[2] == ","){
    var num = 2
}
else {
    var num = Number(m[2])
}

let origin = ["", "p", "b", "v"]
let turn1 = ["\\lparen", "\\lbrack", "\\lbrace", "\\lvert"]
let turn2 = ["\\rparen", "\\rbrack", "\\rbrace", "\\rvert"]

var result = "\\left" + Turn(origin, turn1, m[1]) + " $"
let i = 1

while (i < num){
    var temp = String(i) + ", $";
    result += temp;
    i += 1
}

rv = result + String(num) + "\\right" + Turn(origin, turn2, m[1]) + "$0"
``
endsnippet

这个还算简单,从正则表达式看首先是基础模式 zb,取「坐标」拼音。

然后有四种模式符,分别是默认空代表的 ()\lparen \rparenp 代表的 []\lbrack \rbrackb 代表的 {}\lbrace \rbrace 以及 v 代表的 \lvert \rvert 这括号四大天王。

最后是界定范围,包含了特殊的 ., , 以及普通的单数字。. 代表 1,即单内容情形,而 , 代表 2,即一般的坐标情形。

从代码中可以看出来,数字为多少,就会产生多少个 tabstop。例如说 zb3 展开来就是 \left\lparen ␣, ␣, ␣\right\rparen␣,按顺序依次跳转。

还有一个是类似的,不过是模式符变成了实际的分界符匹配,因为有时候会有半开半闭区间的情形,左右分界符未必是一个类型。

然后是一个我有点看昏了的代码,要不是有点注释真不想看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 输入zbb+选项获得大集合
## n选项产生初始2末端1的大集合模板;可修改下标
## (数字1,数字2)选项产生初始数字1末端数字2的大集合模板;下标从1开始,n结束;只可修改结束下标;支持输入数字范围最小为1,最大理论为无穷大,实际过大会报错“ParseError: KaTeX parse error: Too many expansions: infinite loop or need to increase maxExpand setting”
## [数字1,数字2]选项产生初始数字1 +1末端数字2 +1的大集合模板;下标从0开始,n结束;只可修改结束下标;支持输入数字范围最小为0,最大理论为无穷大,实际过大会报错“ParseError: KaTeX parse error: Too many expansions: infinite loop or need to increase maxExpand setting”
context math(context)
snippet `zbb(n|\(([1-9]\d*,[1-9]\d*)\),|\[(\d+,\d+)\],)` "集合" Ai
``
if (m[1] == "n"){
    rv = "\\left\\{$1_${2:1}, $1_${3:2}, \\cdots, $1_${4:n}\\right\\\\}$0";
}
else if (/^\(\d+,\d+\),$/.test(m[1]) == true){
    let num1 = Number(/(?<=\()\d+(?=,)/.exec(m[1]));
    let num2 = Number(/(?<=,)\d+(?=\),)/.exec(m[1]));
    let result = "\\left\\{";
    let i = 1;
    let j = 1;

    while (i <= num1){
        result += "$1_{" + String(i) + "}, ";
        i += 1;
    };

    result += "\\cdots, ";

    while (j < num2){
        result += "$1_{$2-" + String(num2 - j) + "}, ";
        j += 1
    };

    result += "$1_${2:n}\\right\\\\}";
    rv = result
}
else if (/^\[\d+,\d+\],$/.test(m[1]) == true){
    let num1 = Number(/(?<=\[)\d+(?=,)/.exec(m[1]));
    let num2 = Number(/(?<=,)\d+(?=\],)/.exec(m[1]));
    let result = "\\left\\{";
    let i = 0;
    let j = 0;

    while (i <= num1){
        result += "$1_{" + String(i) + "}, ";
        i += 1;
    };

    result += "\\cdots, ";

    while (j < num2){
        result += "$1_{$2-" + String(num2 - j) + "}, ";
        j += 1
    };

    result += "$1_${2:n}\\right\\\\}";
    rv = result
}
``
endsnippet

解释这个 snippet 之前还是得先看看用例是什么。在书写过程中,我逐渐发现时常需要写像 {a1,a2,,an}\left\lbrace a_1, a_2, \dots, a_n \right\rbrace 之类的公式,于是想能不能将其简化成一个 snippet。

基础模式是 zbb,后面一整块都是选项,分成了三种类型,n 模式、(a,b) 模式与 [a,b] 模式。

n 模式就是我上面讲的那个用例了,tabstop 1 会影响三块,然后 tabstop 2-4 可以分别尝试修改下标,默认就是 1, 2, n 了。

然后还有一种模式就是 {a1,a2,a3,an2,an1,an}\left\lbrace a_1, a_2, a_3 \dots, a_{n-2}, a_{n-1}, a_n \right\rbrace,这种可以靠 zbb(3,3) 生成。如果是零开头的 {a0,a1,a2,an2,an1,an}\left\lbrace a_0, a_1, a_2 \dots, a_{n-2}, a_{n-1}, a_n \right\rbrace,则可以靠 zbb[2,2] 生成。

从代码可以看出来我当时确实不怎么会 JavaScript,也确实沉迷正则,一个非常固定的模式还要靠正则提取数字。

上下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
context math(context)
snippet `(?<=[A-Za-z])(\d)` "自动数字下标" Am
_``rv = m[1]``
endsnippet

context math(context)
snippet `(?<=[A-Za-z])_(\d\d)` "自动数字下标" Am
_``rv = "{" + m[1] + "}"``
endsnippet

context math(context)
snippet `(?<=[A-Za-z])_\{(\d+)\}(\d)` "自动数字下标" Ai
_{``rv = m[1] + m[2]``}
endsnippet

context math(context)
snippet `(?<=[A-Za-z]),(i|k|p|q|m|n)` "自动变量下标" wAm
_``rv = m[1]``
endsnippet

context math(context)
snippet ,, "下标" iAm
_{$1}$0
endsnippet

context math(context)
snippet `_(.);` "取消下标" Ai
``
rv = m[1]
``
endsnippet

context math(context)
snippet `'([\da-zA-Z-])` "数字上标" iAm
^``rv = m[1]``
endsnippet

context math(context)
snippet `(?<=[A-Za-z\d])\^((?:\d|-)\d)` "自动数字上标" Am
^``rv = "{" + m[1] + "}"``
endsnippet

context math(context)
snippet `(?<=[A-Za-z\d])\^\{(-?\d+)\}(\d)` "自动数字上标" Ai
^{``rv = m[1] + m[2]``}
endsnippet

context math(context)
snippet '' "上标" iAm
^{$1}$0
endsnippet

非常常用了,在 snippets 中用 ' 表示上标、, 表示下标的习惯,一直沿用至今。

矩阵

这段是真麻了,不想看代码了,复制介绍吧(为了观感进行了格式微调):

矩阵功能(高级功能由于 HyperSnips 的缺陷无法完成)(麻了,看不懂自己制作的矩阵函数介绍)

  • 基本格式(\XXX\ 意味着 XXX 可以省略):选项+mat/M/mm+\['内容']\+\('尺寸')\+.
    • 选项
      • 选项包括 m(无边框矩阵)、p(小括号矩阵)、b(中括号矩阵)、B(大括号矩阵)、v(行列式)、V(范数)
    • 内容
      • 内容可以为空,为空时没有默认值(也可以设置为空默认值)
      • 内容支持使用 [ ] 包裹,其间所有内容会作为矩阵所有地方的默认值,禁止在 [] 中使用 []
      • 内容支持使用 [ ]& 包裹,可以使用任何符号(但禁止在其中出现 ]&,实在需要出现【很少有这个需求吧】加个空格就可以),包括中括号等
      • 内容还支持 _单字符 的格式,以便于输入单一数字
      • 内容也支持直接输入 单字符 的格式,以便于快速输入单变量(但并不推荐,因为修改不如空矩阵方便)
      • 内容也支持 _{数字} 的格式,以便于输入多个数字作为默认值
      • 内容新支持直接输入内容,但不允许同时以 [ 开头和以 ] 结尾或者以 ( + 数字 + ) 结尾或者以数字结尾。(支持输入中括号)
      • 内容新支持以 & 结尾,可以将先前所有内容都作为内容处理,无禁忌(但不允许空格,如需要空格还是需要用中括号)
      • 内容以 & 表示空字符,如果需要修改矩阵尺寸而不想加入矩阵默认值时可以使用
      • 内容(无中括号)如需要输入 &,正常输入即可;如需在结尾输入,只需多输入一个即可。
      • 中括号外超过一个 & 会被视为直接输入文本处理(如果中括号内有空格会不处理),因此如需输入任何符号,请都在中括号内完成
      • 内容与尺寸有格式冲突的地方,在仅出现一个时,会被当作内容处理。例如 pmm_3. -> 默认值为 3 的 2×2 矩阵,而不是空默认值的 3×3 矩阵;但可以用 pmm(3). -> 空默认值的 3×3 矩阵
    • 尺寸
      • 尺寸默认值为 2×2,可以修改。在没有尺寸输入情况下使用默认值
      • 尺寸支持使用 () 包裹,可以输入单个值(例如 (12) ),或输入两个值(例如 (6,12) ),输入两个值要求用逗号分隔,不允许出现空格。只有一个值 n 会形成 n×n 矩阵,两个值 m,n 会形成 m×n 矩阵(m 行 n 列矩阵)
      • 尺寸还支持 _数字 的格式,以便于输入单一数字,其中 _ 可以省略
      • 尺寸也支持 _{数字} 的格式,以便于输入多个数字作为尺寸值(一般不会用到这么大的矩阵)
    • 激活符是 . ,可修改
    • 其他
      • 当内容为希腊字母或单字母时,自动添加下标,并且 TabStop 停留在下标,若不想要自动添加下标,可以加两个及以上个空格(中括号内),或末尾加入一个 & (中括号内),或末尾加入两个 & (无中括号)

对应的正则表达式是 (m|p|b|B|v|V)(?:M|mm)(\[.+\]&|\[.+\]|_?.|_\{\d+\}|.*[^\d ]&?)?((?:_?\d)|(?:_\{\d+\})|(?:\(.+\)))?\.。我现在写稍微复杂一点的正则表达式,捕获组是一定要命名的,现在这个正则以及下面的代码完全不想去解读一下了。

具体代码

首先定义了一个矩阵函数 gen_mat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function gen_mat(row, column, string, gen_opt, opt) {
    if (gen_opt == 0){
        let results = "";
        let order = 1;
        for (var i = 0; i < row; i ++){
            results += opt;
            for(var j = 0; j < column - 1; j ++){
                results += "${" + String(order) + ":" + string + "} & ";
                order ++;
            }
            results += "${"+ String(order) + ":" + string + "} \\\\" + "\\ ";
            order ++;
        }
        return results;
    }
    else if (gen_opt == 1){
        let results = "";
        let order = 1;
        for (var i = 0; i < row; i ++){
            results += opt;
            for(var j = 0; j < column - 1; j ++){
                results += string + "_{${" + String(order) + ":" + String(order) + "}} & ";
                order ++;
            }
            results += string + "_{${" + String(order) + ":" + String(order) + "}} \\\\" + "\\ ";
            order ++;
        }
        return results;
    }
}

然后是具体 snippets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
priority 1
context display_math(context)
snippet `(m|p|b|B|v|V)(?:M|mm)(\[.+\]&|\[.+\]|_?.|_\{\d+\}|.*[^\d ]&?)?((?:_?\d)|(?:_\{\d+\})|(?:\(.+\)))?\.` "矩阵" Ai
``
if (typeof(m[2]) === "undefined" || m[2] == "&"){
    var string = "";
}
else if (/^\[.+?\]&$/.test(m[2]) == true){
    var string = String(/(?<=\[).+?(?=\]&)/.exec(m[2]));
}
else if (/^\[.+?\]$/.test(m[2]) == true){
    var string = String(/(?<=\[).+?(?=\])/.exec(m[2]));
}
else if (/^_\d$/.test(m[2]) == true){
    var string = String(/(?<=_)\d/.exec(m[2]));
}
else if (/^_\{\d+\}$/.test(m[2]) == true){
    var string = String(/(?<=_\{)\d+(?=\})/.exec(m[2]));
}
else if (/&$/.test(m[2]) == true){
    var string = m[2].slice(0,-1);
}
else {
    var string = m[2];
};

if (/(^\\[a-zA-Z]+\s?$|^[a-zA-Z]\s?$)/.test(string) == true){
    var gen_opt = 1;
}
else {
    var gen_opt = 0;
}

string = string.replace(/\}/g, '\\}');
string = string.replace(/\s*$/g, '');
string = string.replace(/&$/, '');

if (m[1] == "m"){
    var option = "";
}
else {
    var option = m[1];
};

if (typeof(m[3]) === "undefined"){
    var rnum = 2;
    var cnum = 2;
}
else {
    if (/^\(\d+,\d+(?:\))$/.test(m[3]) == true){
        var rnum = Number(/(?<=\()\d+(?=,\d+\))/.exec(m[3]));
        var cnum = Number(/(?<=\(\d+,)\d+(?=\))/.exec(m[3]));
    }
    else if (/^\(\d+(?:\))$/.test(m[3]) == true){
        var rnum = Number(/(?<=\()\d+(?=\))/.exec(m[3]));
        var cnum = Number(/(?<=\()\d+(?=\))/.exec(m[3]));
    }
    else if (/_\d$/.test(m[3]) == true){
        var rnum = Number(/(?<=_)\d/.exec(m[3]));
        var cnum = Number(/(?<=_)\d/.exec(m[3]));
    }
    else if (/\d$/.test(m[3]) == true){
        var rnum = Number(m[3]);
        var cnum = Number(m[3]);
    }
    else if (/^_\{\d+\}$/.test(m[3]) == true){
        var rnum = Number(/(?<=_\{)\d+(?=\})/.exec(m[3]));
        var cnum = Number(/(?<=_\{)\d+(?=\})/.exec(m[3]));
    };
};

let result = "\\begin{" + option + "matrix}" + gen_mat(rnum, cnum, string, gen_opt, "\n") + "\n\\end{" + option + "matrix}";
rv = result;
``
endsnippet

priority 1
context inline_math(context)
snippet `(m|p|b|B|v|V)(?:M|mm)(\[.+\]&|\[.+\]|_?.|_\{\d+\}|.*[^\d ]&?)?((?:_?\d)|(?:_\{\d+\})|(?:\(.+\)))?\.` "矩阵" Ai
``
if (typeof(m[2]) === "undefined" || m[2] == "&"){
    var string = "";
}
else if (/^\[.+?\]&$/.test(m[2]) == true){
    var string = String(/(?<=\[).+?(?=\]&)/.exec(m[2]));
}
else if (/^\[.+?\]$/.test(m[2]) == true){
    var string = String(/(?<=\[).+?(?=\])/.exec(m[2]));
}
else if (/^_\d$/.test(m[2]) == true){
    var string = String(/(?<=_)\d/.exec(m[2]));
}
else if (/^_\{\d+\}$/.test(m[2]) == true){
    var string = String(/(?<=_\{)\d+(?=\})/.exec(m[2]));
}
else if (/&$/.test(m[2]) == true){
    var string = m[2].slice(0,-1);
}
else {
    var string = m[2];
};

if (/(^\\[a-zA-Z]+\s?$|^[a-zA-Z]\s?$)/.test(string) == true){
    var gen_opt = 1;
}
else {
    var gen_opt = 0;
}

string = string.replace(/\}/g, '\\}');
string = string.replace(/\s*$/g, '');
string = string.replace(/&$/, '');

if (m[1] == "m"){
    var option = "";
}
else {
    var option = m[1];
};

if (typeof(m[3]) === "undefined"){
    var rnum = 2;
    var cnum = 2;
}
else {
    if (/^\(\d+,\d+(?:\))$/.test(m[3]) == true){
        var rnum = Number(/(?<=\()\d+(?=,\d+\))/.exec(m[3]));
        var cnum = Number(/(?<=\(\d+,)\d+(?=\))/.exec(m[3]));
    }
    else if (/^\(\d+(?:\))$/.test(m[3]) == true){
        var rnum = Number(/(?<=\()\d+(?=\))/.exec(m[3]));
        var cnum = Number(/(?<=\()\d+(?=\))/.exec(m[3]));
    }
    else if (/_\d$/.test(m[3]) == true){
        var rnum = Number(/(?<=_)\d/.exec(m[3]));
        var cnum = Number(/(?<=_)\d/.exec(m[3]));
    }
    else if (/\d$/.test(m[3]) == true){
        var rnum = Number(m[3]);
        var cnum = Number(m[3]);
    }
    else if (/^_\{\d+\}$/.test(m[3]) == true){
        var rnum = Number(/(?<=_\{)\d+(?=\})/.exec(m[3]));
        var cnum = Number(/(?<=_\{)\d+(?=\})/.exec(m[3]));
    };
};

let result = "\\begin{" + option + "matrix}" + gen_mat(rnum, cnum, string, gen_opt, "\t") + "\\end{" + option + "matrix}";
rv = result;
``
endsnippet

不过我当时其实好像用不到矩阵,这些 snippets 纯粹是为写而写,其实并不实用。要说的话,应该是受到了 Vim: 最好的 Latex 编辑器 - 知乎 这篇文章的启发。

而在大学的时候,矩阵 snippets 已经成为一个必需品,线性代数大量地使用。但当时 HyperSnips 转向 UltiSnips 的时候,为了尽快完成迁移,并没有将这坨玩意一同迁移。于是制作一个新的矩阵 snippets 就提上了日程:Add matrix snippets · Issue #4 · pilgrimlyieu/Snippets

沿用了当时的基础模式 mm。当时制定的目标如下:

矩阵片段

  • 格式(?xxx? 代表 xxx 可省略)
    • `form` + mm + ?`option`;? + ?`size`? + ?[`content`]&?
  • 参数
    • form
      • m: 无边框矩阵
      • p: 小括号边框矩阵
      • b: 中括号边框矩阵
      • B: 大括号边框矩阵
      • v: 行列式
      • V: 范数
    • option
      • 格式(优先级自上而下)
        • O: 零矩阵。全为 0。无 tabstop。
        • i: (方阵)单位矩阵。对角线全为 1。无 tabstop
        • d: (方阵)对角矩阵。对角线全为 content。对角线有 tabstop
        • D: (方阵)反对角矩阵。反对角线全为 content。反对角线有 tabstop
        • t: (方阵)上三角矩阵。上三角全为 content。上三角有 tabstop
        • T: (方阵)下三角矩阵。下三角全为 content。下三角有 tabstop
        • c: (方阵)常对角矩阵。首行、列全为 content。首行、列有 tabstop
        • c: (方阵)常反对角矩阵。首行、末列全为 content。首行、末列有 tabstop
        • s: (方阵)对称矩阵。上三角除除第一个元素对角线全为 content。上三角除除第一个元素对角线上三角有 tabstop
        • S: (方阵)对称矩阵。上三角全为 content。上三角有 tabstop
      • 选项(加入以下选项代表与默认选项相反)
        • .: (未完成)自动省略号
        • o: 填充 0 元素
        • p: 为 0 元素添加 tabstop(共用一个)
        • P: 为 0 元素添加 tabstop(各自独立)
        • y: 格式 tabstop
    • size
      • m×n 为 m 行 n 列
      • 默认 3×3
      • n n×n
      • mn m×n
    • content
      • 预填充内容
      • 内容包含空格时需增加 & 明确内容结束
      • 关键词
        • \r: 行序号
        • \c: 列序号
        • \n: 序号
        • `\x?`: 用「`」包裹可对序号进行 + - * / 的简单运算

最终主要在下面两个 PR 中完成了:

看了下日期,是在开学后不久的国庆假期完成了。而前面的一个月则是在纸上进行了一些畅想与构思。

然而因为我的代码水平太差,加上对 UltiSnips 的不熟悉(即便我翻看了几遍文档),最终只是勉强实现了静态模板,上面憧憬的动态变化则是没什么思路。

有点感慨,我现在肯定是不会像当时那样去钻研、反复调试代码了,稍有挫折那就是直接丢给 AI,懒得思考了。当然,虽然当时没有用 AI Chat 或是 AI Agent 一下次唰唰弄完了,但也 Tab Tab 弄了很多 cases。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
global !p

from mdtex.scopes import math, display_math
from mdtex.matrix import *

endglobal

priority 1
context "math()"
snippet "(?a)\b([mpbBvV]mm\d)" "优化矩阵 Matrix Tip" Ar
`!p snip.rv = match.group(1)`
endsnippet

post_jump "generate_matrix(match.group('form'), match.group('option'), match.group('size'), match.group('content1') or match.group('content2') or ' ', snip)"
context "math()"
snippet "(?a)\b(?P<form>[mpbBvV])mm(?P<option>([OidDtTcCsS.opPy]*;?))(?P<size>\d{,2})(\[(?P<content1>.*?)\]|(?P<content2>[^\s]*?))&" "矩阵 Matrix" r
endsnippet

第一个 snippet 我看到的时候都愣了一下,不知道有啥用。后面才反应过来,前面有自动下标,在 HyperSnips 中选择了支持 _数字_{数字} 的处理方式,而在 UltiSnips 版本中选择了在这种情形放弃自动下标。

因为为捕获组命名了,这个正则也比较浅显易懂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(?a)                           # 启用 ASCII 模式
\b                             # 匹配单词边界
(?P<form> [mpbBvV] )           # 参数 form:匹配 m, p, b, B, v, V 中的一种类型
mm                             # 固定模式 "mm"
(?P<option>                    # 参数 option:(下面的捕获组有点意义不明)
  ( [OidDtTcCsS.opPy]* ;? )    # 匹配 0 或多个选项字符,后面可跟一个可选的分号
)
(?P<size> \d{,2} )             # 参数 size:匹配 0 到 2 位数字
(                              # 内容匹配分支:
    \[ (?P<content1> .*? ) \]  #   方式 1:匹配方括号内的内容(content1)
    |                          #   或者
    (?P<content2> [^\s]*? )    #   方式 2:匹配非空白字符直到结束(content2)
)
&                              # 固定模式 "&"

重新看了一下代码,虽然仅从格式上来看还可以,但也暴露了当时的一个坏习惯——压行。

然而这么「强大」的矩阵 snippets,我却基本上没有用过 option 与 content,最多改改 size。

现实中最常用的,就是知道一个大小,如 3×43 \times 4,然后 bmm34&,一个一个填充进去。

但线代笔记总归是有很多常见类型的矩阵,我后面的做法就是简简单单为其定制新的 snippets,而非在这里写复杂的选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
context "math()"
snippet "(?a)\b(?P<form>[mpbBvV])(?P<n1>[a-zA-Z])(?P<n2>[a-zA-Z])(?P<v>[a-zA-Z]?)\." "n1xn2 矩阵 n1xn2 Matrix" r
`!p
form = match.group('form') if match.group('form') != 'm' else ''
v = match.group('v') or 'a'
matrix = [
    [v + "_{1 1}", v + "_{1 2}", "\\cdots", v + "_{1 " + match.group('n2') + "}"], 
    [v + "_{2 1}", v + "_{2 2}", "\\cdots", v + "_{2 " + match.group('n2') + "}"], 
    ["\\vdots", "\\vdots", "\\ddots", "\\vdots"], 
    [v + "_{" + match.group('n1') + " 1}", v + "_{" + match.group('n1') + " 2}", "\\cdots", v + "_{" + match.group('n1') + " " + match.group('n2') + "}"]
]
snip.rv = matrix_template(form, matrix, display_math())
`
endsnippet

context "math()"
snippet "(?a)\bd(?P<form>[mpbBvV])(?P<n>[a-zA-Z])(?P<v>[a-zA-Z]?)\." "对角矩阵 Diagonal Matrix" r
`!p
form = match.group('form') if match.group('form') != 'm' else ''
v = match.group('v') or 'a'
matrix = [
    [v + "_{1 1}", "0", "\\cdots", "0"], 
    ["0", v + "_{2 2}", "\\cdots", "0"], 
    ["\\vdots", "\\vdots", "\\ddots", "\\vdots"], 
    ["0", "0", "\\cdots", v + "_{" + match.group('n') + " " + match.group('n') + "}"]
]
snip.rv = matrix_template(form, matrix, display_math())
`
endsnippet

不过还是有点感慨,因此还是在这里展示一些高级用例吧。里面都添加了 y 选项,因此直接就完成了输入,无法逐个修改 tabstop。

pmmys3a_{\r\c}&

(a11a12a13a21a11a23a31a32a11)\begin{pmatrix} a_{11} & a_{12} & a_{13} \\ a_{21} & a_{11} & a_{23} \\ a_{31} & a_{32} & a_{11} \end{pmatrix}

bmmyd4\lambda_{\r}&

[λ10000λ20000λ30000λ4]\begin{bmatrix} \lambda_{1} & 0 & 0 & 0 \\ 0 & \lambda_{2} & 0 & 0 \\ 0 & 0 & \lambda_{3} & 0 \\ 0 & 0 & 0 & \lambda_{4} \end{bmatrix}

vmmy3a^{n`('+'*(a>0)+str(a))if(a:=\n-2*\c*\r)else''`}&

an1an2an3anan3an6an+1an4an9\begin{vmatrix} a^{n-1} & a^{n-2} & a^{n-3} \\ a^{n} & a^{n-3} & a^{n-6} \\ a^{n+1} & a^{n-4} & a^{n-9} \end{vmatrix}

分数

如果我没记错的话,我第一个将 Castel 的 Python 代码翻译成 JavaScript 代码的 snippet 就是分数代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
context math(context)
snippet `((\d+)|(\d*)(\\)?([A-Za-z]+)((\^|_)(\{\d+\}|\d))*|[a-zA-Z0-9]+!)/` "简单分数" Am
\dfrac{``rv = m[1].replace(/\}/g, '\\}')``}{$1}$0
endsnippet

context math(context)
snippet `//` "分数" wAm
\\dfrac{$1}{$2}$0
endsnippet

context math(context)
snippet `^.*\)/` "复杂分数" Am
``
//From @Castel
//定义变量str等于从行头到/的所有字符
let str = m[0];
//重定义变量str等于从行头到)的所有字符
str = str.slice(0, -1);
//似乎无用
//let lastIndex = str.length - 1;

//定义变量depth等于0
let depth = 0;
//定义序数变量i等于变量str的长度减一,即str的最后一个字符的序数
let i = str.length - 1;

//从str后往前循环遍历
while (true) {
    //如果字符为),depth加一,第一轮循环必加一
    if (str[i] == ')') depth += 1;
    //如果字符为(,depth减一
    if (str[i] == '(') depth -= 1;
    //如果depth为0,退出循环(括号饱和)
    if (depth == 0 || i == -1) break;
    //序数减一,继续循环
    i -= 1;
}

//定义输出结果
if (i == -1){
    var results = m[0]
}
else {
    var results = str.slice(0, i) + "\\dfrac{" + str.slice(i + 1, -1) + "}{$1}$0";
}
rv = results.replace(/\}/g, '\\}').replace(/\\\\/g, '\\\\\\\\');
``
endsnippet

回到 UltiSnips 后,我对大型分数又进行了点小改动,与 Castel 的版本形成了细微的差异(Large Bracket Function in markdown/math_operators_fraction.snippets works in a strange behavior in a case · Issue #1 · pilgrimlyieu/Snippets):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
context "math()"
snippet "\(.*\)/" "大型括号分数 Large Parenthes Fraction" rA
`!p
string = match.group(0)[:-1]
depth = 0
for i in range(len(string) - 1, -1, -1):
    char = string[i]
    if char == ')':
        depth += 1
    elif char == '(':
        depth -= 1
    if not depth:
        break
snip.rv = string[:i] + "\\dfrac{" + string[i + 1:-1] + "}"
`{$1}$0
endsnippet

当时还搞出了非常复杂而无用的分式变形 snippets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# From @yfzhao20
context math(context)
snippet `(\\(frac|dfrac)\{.*\}\{.*\});` "分式变形" iA
``
var str = m[1].replace(/\\frac/g,"").replace(/\\dfrac/g,"").split('');

var depth = 0;
var i = str.length - 1;
var test = 0;

while (i >= 0) {
    if (str[i] == '\}') depth ++;
    if (str[i] == '\{') depth --;
    
    if (test == 2 && !depth){
        if (str[i] == ''){str[i] = '';break;}
        if (str[i] == ''){str[i] = '';break;}
    }
    test += depth ? 0 : 1;
    i --;
}

rv = str.join('').replace(//g,"\\frac").replace(//g,"\\dfrac");
``
endsnippet

context math(context)
snippet `\\dfrac\{(.+)\}\{(.+)\},;` "分式变形" Ai
``
if (m[1].length == 1 || /^((\d+)|(\d*)(\\)?([A-Za-z]+)((\^|_)(\{\d+\}|\d))*|[a-zA-Z0-9]+!)$/.test(m[1]) == true || /^\\(?:sin|cos|tan|cot|arcsin|arccos|arctan|arccot|csc|sec|log|ln|lg|exp|arg|max|min|det)\s?(?:\\[a-zA-Z]+|[a-zA-Z]|\(.+\))$/.test(m[1]) == true){
    var result = m[1].replace(/\}/g, '\\}');
}
else {
    var result = "(" + m[1].replace(/\}/g, '\\}') + ")";
};

result += "/";

if (m[2].length == 1 || /^((\d+)|(\d*)(\\)?([A-Za-z]+)((\^|_)(\{\d+\}|\d))*|[a-zA-Z0-9]+!)$/.test(m[2]) == true || /^\\(?:sin|cos|tan|cot|arcsin|arccos|arctan|arccot|csc|sec|log|ln|lg|exp|arg|max|min|det)\s?(?:\\[a-zA-Z]+|[a-zA-Z]|\(.+\))$/.test(m[2]) == true){
    result += m[2].replace(/\}/g, '\\}');
}
else {
    result += "(" + m[2].replace(/\}/g, '\\}') + ")";
};

rv = result
``
endsnippet

# 暂不支持"\sin x/\cos x,"->"\dfrac{\sin x}{\cos x}"及"\sin\alpha/\cos\alpha,"->"\dfrac{\sin\alpha}{\cos\alpha}"等的形式
context math(context)
snippet `((?:\d+)|(?:\d*)(?:\\)?(?:[A-Za-z]+)(?:(?:\^|_)(?:\{\d+\}|\d))*|[a-zA-Z0-9]+!|\([^\(\)]+\))/((?:\d+)|(?:\d*)(?:\\)?(?:[A-Za-z]+)(?:(?:\^|_)(?:\{\d+\}|\d))*|[a-zA-Z0-9]+!|\([^\(\)]+\)),;` "分式变形" Ai
\dfrac{``
let a = m[1].replace(/\}/g, '\\}');

if (/^\(.+\)$/.test(a) == true){
    var result = /(?<=\().+(?=\))/.exec(a);
}
else {
    var result = a;
};

result += "}{";
a = m[2].replace(/\}/g, '\\}');

if (/^\(.+\)$/.test(a) == true){
    result += /(?<=\().+(?=\))/.exec(a);
}
else {
    result += a;
};

result += "}";
rv = result;
``
endsnippet

\frac\dfrac 互转倒还罢了,这个确实现在还在用,但是后面的 \frac/ 的转换就纯属是闲的没事干了。

UltiSnips 仅保留了一种:

1
2
3
4
5
# frac <-> dfrac
context "math()"
snippet "(\\(d?frac)\{.*\}\{.*\})" "分式变形 Fraction Transformation" r
`!p snip.rv = command_swap(match.group(1), '\\frac', '\\dfrac', 2)`
endsnippet

command_swap 定义在了 mdtex.cmdsub 中。当时甚至进行了时间测试,只是非常潦草。

化学

学化学的时候,有时候要写轨道表达式之类的,于是我手搓了点宏。

还记得那会放在官网上,放大很多倍,测试很多情形,来看显示有没有问题。不过比较可惜的是,最后还是没弄做到所有用例都非常完美地显示,有些地方的线条就是会粗一点,最后只能无奈顺从了。

下面是最终的宏,现在已经注释掉废弃不用了:

  • 电子对 \pe\kern-0.023em\boxed{\uparrow\downarrow}\kern-0.023em
    • \kern-0.023em\boxed{\uparrow\downarrow}\kern-0.023em
  • 错误电子对 \npe\kern-0.023em\boxed{\uparrow\uparrow}\kern-0.023em
    • \kern-0.023em\boxed{\uparrow\uparrow}\kern-0.023em
  • 错误电子对 \nnpe\kern-0.023em\boxed{\downarrow\downarrow}\kern-0.023em
    • \kern-0.023em\boxed{\downarrow\downarrow}\kern-0.023em
  • 单电子 \se\kern-0.023em\boxed{\kern0.25em\uparrow\kern0.25em}\kern-0.023em
    • \kern-0.023em\boxed{\kern0.25em\uparrow\kern0.25em}\kern-0.023em
  • 单电子 \nse\kern-0.023em\boxed{\kern0.25em\downarrow\kern0.25em}\kern-0.023em
    • \kern-0.023em\boxed{\kern0.25em\downarrow\kern0.25em}\kern-0.023em
  • 空电子 \oe\kern-0.023em\boxed{\kern0.25em\phantom\uparrow\kern0.25em}\kern-0.023em
    • \kern-0.023em\boxed{\kern0.25em\phantom\uparrow\kern0.25em}\kern-0.023em

自然有一些 snippets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
context math(context)
snippet `([\dn])(s|p|d|f|g)ee` "轨道表示式" Ai
``
let string = "\\normalsize " + m[1] + "\\ce{" + m[2] + "}"
let origin = ["s", "p", "d", "f", "g"]
let turn = [1, 3, 5, 7, 9]
let num = Turn(origin, turn, m[2])
rv = "\\overset{" + string + "}{" + "\\pe ".repeat(num) + "}"
``
endsnippet

context math(context)
snippet `([\dn])(s|p|d|f|g)eo` "轨道表示式" Ai
``
let string = "\\normalsize " + m[1] + "\\ce{" + m[2] + "}"
let origin = ["s", "p", "d", "f", "g"]
let turn = [1, 3, 5, 7, 9]
let num = Turn(origin, turn, m[2])
var str = "\\overset{" + string + "}{"
let i = 1
while (i <= num){
    str += "${" + i + ":\\oe }"
    i ++
}
rv = str + "}"
``
endsnippet

context math(context)
snippet `(\d)ee` "轨道表示式" Ai
``
let string = "\\normalsize $1"
let origin = ["s", "p", "d", "f", "g"]
let num = Number(m[1])
rv = "\\overset{" + string + "}{" + "\\oe ".repeat(num) + "}"
``
endsnippet

例如说 2pee 可以生成:

2p\def\pe{\kern-0.023em\boxed{\uparrow\downarrow}\kern-0.023em} \overset{\normalsize2\ce{p}}{\pe\pe\pe}

文本模式

上面的都是数学公式的 snippets,实际上还有文本模式的。

不知道是从哪里学习的,也许是当时还不是学长的 OrangeX4,我起初进入数学模式用的是 imdm,分别代表 inline-math 与 display-math。

不过后面实际使用中注意到,以 im 开头的单词并不少见,于是就决定改改。随便就改成了 lm,并一直延续至今。下面是 HyperSnips 的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
context text(context)
snippet `([^a-zA-Z0-9]?)(?<![a-zA-Z])(lm|LM)` "行间公式" Aw
``
if (m[1] == " " || m[1] == "" || /(,||\.||;||:||'||"|\[|\]|\(|\)|\{|\}|\<|\>||!|\-)/.test(m[1]) == true){
    rv = m[1]
}
else {
    rv = m[1] + " "
}
``$$1$ $0
endsnippet

context text(context) 
snippet `^\s*(dm|DM)` "展示公式" A
$$
$1
$$
$0
endsnippet

从这可以看出来,在 HyperSnips 最后时段,我已经改过来了习惯,不会在全角标点后加入多余的空格了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 切换字母大小写(不支持空格分隔)
## 输入字母(串)+ "~"将字母串变为大写或小写(混杂情况取决于第一个字母)
## 输入字母(串)+ "·"将字母串首字母变大写或小写
## 输入字母(串)+ "`"将字母串尾字母变大写或小写(适合不开大写锁定输入大写)
snippet `([a-zA-Z]+)(\u0060|\u007E|\u00B7)` "切换字母大小写" Ai
``
if (m[2] == "~"){
    if (/^[a-z]/.test(m[1]) == true){
        rv = m[1].toUpperCase()
    }
    else if (/^[A-Z]/.test(m[1]) == true){
        rv = m[1].toLowerCase()
    };
}
else if (m[2] == "·"){
    if (/^[a-z]/.test(m[1]) == true){
        rv = m[1].slice(0, 1).toUpperCase() + m[1].slice(1)
    }
    else if (/^[A-Z]/.test(m[1]) == true){
        rv = m[1].slice(0, 1).toLowerCase() + m[1].slice(1)
    };
}
else if (m[2] == "`"){
    if (/[a-z]$/.test(m[1]) == true){
        rv = m[1].slice(0, -1) + m[1].charAt(m[1].length -1).toUpperCase()
    }
    else if (/[A-Z]$/.test(m[1]) == true){
        rv = m[1].slice(0, -1) + m[1].charAt(m[1].length -1).toLowerCase()
    };
}
else {
    rv = m[1] + m[2]
}
``
endsnippet

笑,我当时其实还不知道,可以直接 Shift + 字母输入大写,不用大写锁定。

context

差不多也就介绍完比较特殊的一些模式。下面是 context 相关内容。

UltiSnips 中的数学代码片段,为了仅在数学模式中被触发,用到了 context,即对应表达式为真时才会触发。UltiSnips 的版本借助了 VimTeX 插件 对不同上下文进行了区别。

而 VS Code 借助语法高亮的作用域,可以实现类似的效果。前人研究出来了这样的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
////数学模式函数(包括行间和展示模式)

function math(context) {
    //return context.scopes.some(s => s.startsWith("meta.embedded.block.katex")) || context.scopes.some(s => s.startsWith("punctuation.definition.math.inline.markdown")) || context.scopes.some(s => s.startsWith("markup.inserted.math.display.markdown"));
    return context.scopes.some(s => s.includes("math"));
}

////行内数学模式函数

function inline_math(context){
    return context.scopes.some(s => s.includes("math.inline"));
}

////展示数学模式函数

function display_math(context){
    return context.scopes.some(s => s.includes("math.block")) | context.scopes.some(s => s.includes("math.display"));
}

////文本模式函数

function text(context) {
    //return !context.scopes.some(s => s.startsWith("meta.embedded.block.katex")) && !context.scopes.some(s => s.startsWith("punctuation.definition.math.inline.markdown")) && !context.scopes.some(s => s.startsWith("markup.inserted.math.display.markdown"));
    return !context.scopes.some(s => s.includes("math"));
}

scope 的具体名称可以通过 Developer: Inspect Editor Tokens and Scopes 命令进行查看。

而下面则是我目前的有关 context 的部分常用函数(为了简化显示进行了部分调整,详细可查看撰写时的情况):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 数学模式 Math Mode
def math():
    except_conditions = [cmd('textcolor'), cmd('operatorname')]
    return all([vim.eval('vimtex#syntax#in_mathzone()') == '1', not any(except_conditions)])

# 纯数学模式 Pure Math Mode
def pure_math():
    extra_conditions = [not_chem(), not_unit()]
    return all([math(), all(extra_conditions)])

def extcal():
    return any([env(environment) for environment in external_environments])

# 行内公式模式 Inline Math Mode
def inline_math() -> bool:
    return vim.eval("vimtex#syntax#in('texMathZone[LT]I')") == '1'

# 行间公式模式 Display Math Mode
def display_math() -> bool:
    return vim.eval("vimtex#syntax#in('texMathZone[LT]D')") == '1'

# 化学模式 Chemistry Mode
def chem() -> bool:
    return vim.eval("get(vimtex#cmd#get_current(), 'name')") == '\\ce' and math()

# 非化学模式 Not Chemistry Mode
def not_chem() -> bool:
    return vim.eval("get(vimtex#cmd#get_current(), 'name')") != '\\ce'

# 单位模式 Unit Mode
def unit() -> bool:
    return vim.eval("get(vimtex#cmd#get_current(), 'name')") == '\\pu' and math()

# 非单位模式 Not Unit Mode
def not_unit() -> bool:
    return vim.eval("get(vimtex#cmd#get_current(), 'name')") != '\\pu'

# 文本模式 Text Mode
def text() -> bool:
    return vim.eval('vimtex#syntax#in_mathzone()') == '0'

# 注释模式 Comment Mode
def comment() -> bool: 
    return vim.eval('vimtex#syntax#in_comment()') == '1'

# 特殊命令 Specific Commands
def cmd(name) -> bool:
    return vim.eval("get(vimtex#cmd#get_current(), 'name')") == f'\\{name}'

# 特殊环境 Specific Environments
def env(name) -> bool:
    [x, y] = vim.eval("vimtex#env#is_inside('" + name + "')") 
    return x != '0' and y != '0'

这些调配其实也是经过了一番功夫。为此我还在 VimTeX 那边提了一些 issue,有的让我非常害臊因为是自己的问题,有的则让 VimTeX 引入了新的特性,例如说更为精细的高亮组名称等。

笔记「大业」

这套「工作流」最早形成的数学笔记应该就是我 2021.9.24 写的 《截距式方程相关结论与推导过程》,同时在之后也转录为了博文。

而半成型的则是 2021.6.27 的物理笔记,在上面提及过的记事部分有提一嘴「第一个动量笔记」。

然后从空间的相册中看到,2021.11.5 也写了一篇有关圆锥曲线的笔记,不过不知为何空间时间线中没有显示,仅在相册中记录了。

再然后,有了上面的 HyperSnips 与一番配置后,我实际上在学校不仅可以完成语文的机械式任务,实际上还可以进行笔记的撰写。

我也确实这样做了,在学校写了第一篇也是唯一一篇数学笔记——《偏移法解决圆锥曲线斜率之积/斜率之和为定值过定点问题》。当时其实是我从朋友拿学来的一个方法,甚感精妙,因此根据其内容称其为「偏移法」,后面才知道其实是「齐次化联立」。

笔记图片

根据空间的记载,这是 2021.11.10 日写的笔记:

这份笔记有点特殊,因为这是在期中考试数学考试前的中午(更准确的说,是在地理考试期间)完成的。
因为没有在家里的配置,图片是黑白的,可能不像以前一样养眼。

当时的我记得是在 GitHub 上建了一个 Temps 私有仓库,里面会放一些配置,例如说 snippets 啊之类的。然后就在学校图书馆电脑可以下下来。即将 GitHub 当成了一个远程「云盘」。

居然还找到了仓库的备份,将 git log 打印一下。首次 commit 时间是 2021.11.12,最后一次 2022.5.29。嗯,嗯?那可能最开始是 U 盘?记不清了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
* 542e46d - (HEAD -> main, origin/main, origin/HEAD) Delete KillPDFStrangeCharacters.ahk <Lyieu>
* be310e0 - Delete AnkiDerive.ahk <Lyieu>
* 466dcf8 - Update markdown.snippets <Lyieu>
* 4c59747 - Update markdown.snippets <Lyieu>
* 1ebffe5 - Update markdown.snippets <Lyieu>
* bbf3fb2 - Delete symbol.snippets <Lyieu>
* d2e593c - Update markdown.snippets <Lyieu>
* 81ceafe - Update markdown.snippets <Lyieu>
* ba280c9 - Update symbol.snippets <Lyieu>
* 75790f1 - Rename symbol.snippet to symbol.snippets <Lyieu>
* 9165ff3 - Rename markdown.snippet to markdown.snippets <Lyieu>
* 1e612f9 - Update markdown.snippet <Lyieu>
* 7d06bc1 - Update markdown.snippet <Lyieu>
* 6a25718 - Update symbol.snippet <Lyieu>
* 6bcfd05 - Update KillPDFStrangeCharacters.ahk <Lyieu>
* b1cabe6 - Update KillPDFStrangeCharacters.ahk <Lyieu>
* 35b9150 - Update KillPDFStrangeCharacters.ahk <Lyieu>
* 628256d - Update markdown.snippet <Lyieu>
* 74dc2ef - Update markdown.snippet <Lyieu>
* 73fe515 - Update symbol.snippet <Lyieu>
* ad3c9fc - Update markdown.snippet <Lyieu>
* 49ecdbb - Update markdown.snippet <Lyieu>
* 617ce81 - Update symbol.snippet <Lyieu>
* 86f70d4 - Update symbol.snippet <Lyieu>
* 170962d - Update symbol.snippet <Lyieu>
* 05d51c7 - Update markdown.snippet <Lyieu>
* 96dca8b - Update markdown.snippet <Lyieu>
* 4474792 - Update markdown.snippet <Lyieu>
* 9a23974 - Update markdown.snippet <Lyieu>
* 9143465 - Update symbol.snippet <Lyieu>
* 8fe8301 - Update symbol.snippet <Lyieu>
* 8cac0bb - Create markdown.snippet <Lyieu>
* 09c7850 - Update symbol.snippet <Lyieu>
* 1334d70 - Update and rename symbol.hsnips to symbol.snippet <Lyieu>
* 188eeba - Create symbol.hsnips <Lyieu>
* 5a40837 - Create markdown.snippet <Lyieu>
* de48ea7 - Update AnkiDerive.ahk <Lyieu>
* 9f6a0c8 - Delete MinWinJump.ahk <Lyieu>
* 0ead0d2 - Delete F-WinJump.ahk <Lyieu>
* 6577551 - Update MinWinJump.ahk <Lyieu>
* 22b93a4 - Create AnkiDerive.ahk <Lyieu>
* 6269073 - Update MinWinJump.ahk <Lyieu>
* ec8c459 - Update MinWinJump.ahk <Lyieu>
* b3a1a40 - Update MinWinJump.ahk <Lyieu>
* b80c30c - Create MinWinJump.ahk <Lyieu>
* 6389d4c - Update F-WinJump.ahk <Lyieu>
* a1f64ea - Update F-WinJump.ahk <Lyieu>
* 7334fa7 - Update F-WinJump.ahk <Lyieu>
* 3ebd58f - Update F-WinJump.ahk <Lyieu>
* 7ca3ebf - Create F-WinJump.ahk <Lyieu>
* f54e511 - Update KillPDFStrangeCharacters.ahk <Lyieu>
* 38f124d - Update KillPDFStrangeCharacters.ahk <Lyieu>
* 74713f5 - Update KillPDFStrangeCharacters.ahk <Lyieu>
* af7bdd0 - Create KillPDFStrangeCharacters.ahk <Lyieu>
* a87b4b1 - Delete Origin.py <Lyieu>
* bd01a93 - Delete JJQ.py <Lyieu>
* 60ba29b - Create Origin.py <Lyieu>
* a6aabbe - Create JJQ.py <Lyieu>
* f767d56 - Delete 2.md <Lyieu>
* e08615c - Delete 1.md <Lyieu>
* 1871c11 - Update 2.md <Lyieu>
* 60b9442 - Rename 1 to 1.md <Lyieu>
* 545437a - Create 2.md <Lyieu>
* 50c2ff8 - Create 1 <Lyieu>

从上面笔记的图中其实可以看出来居然还有配图,我想可能是我当时把 GeoGebra 的配置也给一起带过去了。

本篇内容不想细谈 GeoGebra 画图的相关内容,因此仅在这里进行简要的概括。

大概就是我不喜 GeoGebra 默认的那种坐标轴风格,因此自己手搓了一个类似「宏」的东西(保存为了 简易坐标轴.ggt),可以输入数值自己生成一个坐标轴,类似图上的效果。当然其实最后我还是不太满意,但相较于之前已经算是不错了,加上无能为力,这才作罢。

不过我现在就有点好奇了为什么这一篇没转录呢?可能是后面感觉意义不是很大吧?也可能是当时转录的时候没看到。我去好像还真没找到原始文件

rg 了一番,找到了。后面——可能是 2022.2.22——给「偏移法」补了一个脚注,写「即齐次化联立」。当时转录的时候可能略过了这篇。

2021.12.17

与师俱进

讲到了数列,写了一篇有关数列的笔记。似乎是看到了一个知乎回答?反正写了一下:

  1. 1,1,2,2,1, 1, 2, 2, \dots 通项公式 an=n2+1+(1)n14a_n = \dfrac{n}{2} + \dfrac{1 + (-1)^{n-1}}{4}
  2. a1,a1,a2,a2,a_1, a_1, a_2, a_2, \dots 通项公式 bn={a1,n=1i=1n1f(i2+1)f(i2)2[1+(1)i]+a1,n2b_n = \begin{cases} a_1, & n = 1\\ \displaystyle \sum_{i=1}^{n-1} \dfrac{f\left(\frac{i}{2}+1\right) - f\left(\frac{i}{2}\right)}{2}\left[1 + (-1)^{i}\right] + a_1, & n\ge 2 \end{cases}
  3. 1,2,2,1, 2, 2, \dots 通项公式 cn=2n12c_n = \left\lceil \sqrt{2n} - \dfrac{1}{2} \right\rceil

2022.1.9

时隔近两年,又要上网课了。

然后便是「万恶」的网课时期了。

2022.1.9

只能说是天命,在这个特别的日子里,在这个特别的地方,三卡齐聚。

当天还发了另一条说说,是三张校卡的合影。我的校卡丢了几次,补办了几次。结果就在这命运的一天,大概是找回来了第三张(第二张大概补办后不久就找回来了),然后拍了张照。

而且非常有意思的是,最开始的校卡年级写的是「高中2020级」,补办第一次后是「高一X班」,第二次是「高二Y班」,因此三张卡各不相同。后面其实我甚至有想集齐第四张卡写「高三Z班」的念头呢。后面好像这三张卡又丢了,似乎是旧卡?反正最终也没能集齐。

2022.1.20

高级有余,严谨不足

想到了之前的一个问题,并尝试进行了解答。不过现在看来答案应该是有问题的。转录为了博文《多个正整数和与积相等问题的初步讨论》

随后 1.29 又写了一篇笔记《泊松分布带来的一些伽马函数的积分》。内容就是我「终于开始写作业了」[17],做到了一个题目,给了泊松分布的概率函数,然后求一个概率,而我一开始没想到标答的方式,用了一个更复杂的方式进行计算并得到了类似的数值结果,从而以此为假设推导得出了一个「美丽」的公式:

(nx) ⁣dx=2n\int_{-\infty}^{\infty} \dbinom{n}{x} \d x = 2^n

这里的 (nx)\dbinom{n}{x} 实际上就是 Γ(n+1)Γ(x+1)Γ(n+1x)\dfrac{\Gamma(n+1)}{\Gamma(x+1)\Gamma(n+1-x)}

2.23 发了了两篇,一篇是已经转录的《初遇信息熵》,另一篇是一个「思考」,写了红蓝眼睛问题,只不过这一次提及了「共识」与「常识」,当然其实当时还是不太明白。

当然后篇的大部分内容其实是翻阅 MPE 文档注意到的一些有意思的 Markdown 扩展语法——例如说缩略语啊,还有就是对我后面 Markdown 笔记内容有重要影响的 Admonition 了——进而了解到了 MkDocs。当时还收藏了一下,因为 MPE 的 admonition 其实只支持了最为基础的 admonition,还有很多让我艳羡的功能没添加,因此我就想留着后面加。只不过现在虽然 Vibe Coding 有能力了,但却没什么欲望了。

翻到最后居然还有一个算法题,「鸡蛋硬度」。

然后就是漫长的网课划水了,这段时间的主旋律应该是下一章节的内容了,这里还是专注于笔记内容。

四月份返校以后我的空间沉寂下来了,也因为网课时间的摆烂,我的「笔记系统」实质上已经停滞了。看了一看记录,上面十周空当后弄了一个 16 周笔记,然后 17 周再度空缺,18 周 2022.6.24 写了学期最后一次笔记。

再然后应该是四校、分班考、最后一次高中暑假和高三开学了。这段时间竟没有什么文字记录,前几次假期我都是有一些文本记录留下来的,可见这段时间之摆烂。

但即便没有任何文字我也记得我那个暑假干了些什么,四校的时候干了些什么……算是一个新的起点。

但对于我的学习无疑是重创。四校和分班考成绩之烂,我那时其实都不抱期望能进重点班了,最后分班名单出来的时候反而喜出望外,无比庆幸。这个「创伤」其实一直延续到了高三,记得分班考生物好像是倒数当时班上有什么来着的(咦,好像是没及格?[18])、到下面我要提的记录。

至于说后面高三最差的成绩竟然比高一、高二最好成绩都好,那便是后话了。

2022.9.11

庆祝解封&迟到的中秋快乐

直到 9.11(零点刚过)才再度复出,推出了《极限初探》,不过是偷点干正事的时间发散思维。

9.24 晚上本来只是随手记事,结果后面越写越长,直到 9.25 快一点钟才发了出来。这篇最初名为《胡思乱想》的内容也已经转录为了《2022 年 9 月 25 日胡思乱想》

所以说根据时间,说是「2022 年 9 月 24 日胡思乱想」才更为贴切。而我正好是在一年后的 9.24 转录的。

这篇内容里面记录了我不久后的数学笔记发布打算,还有主要就是我对过往的简单回顾——就像我现在这样。

里面的一些内容其实可以跟上面讲的相互印证,也有部分内容可能因为时间更相近抑或是我这里不侧重,因此那边更为精准和详尽。

然后我看到了一篇文章,这篇文章可以说是成就了现在的我,也毁了现在的我。直接把我变成一个极端追求美的人(例如标点规范等方面)。此时我也正式了解了 markdown,于是我抛弃了 LaTeX,使用了 VSCode

这里说的其实就是 Castel 的传世名作。不过因为当时其实消极情绪比较多,用到了「毁」。现在看来其实说过头了。

然后网课开始后就开始划水了,这段时间投身 AutoHotkey,无心学习与钻研,2 月 23 日最后一篇笔记(信息熵、红蓝眼睛等)到 4 月 7 日公开网课成果的笔记空窗期。然后就是到两周前才有了新的一篇笔记,时隔近 7 个月。

这 7 个月我做了什么?最重要的应该就是我放弃了 VSCode,转向了 Vim,也是受那篇文章影响,而且是在返校后进行的(我现在这篇文字就是在 Vim 里写的),导致我成绩几乎一落?丈。直到现在我仍在苦苦摸索。

但你问我,我后悔吗。我不后悔,咎由自取,自食恶果罢了。

啰哩吧嗦半天,是时候结束了。说实话我原本以为我有很多笔记,实际上也就那么几篇罢了,看着逝去的时光,真是呜呼哀哉。

悟以往之不谏,知来者之可追。实迷途其未远,觉今是而昨非。

与君共勉。

感激涕零,不知所言。

当时的心情是什么样子的呢?大概就是网课非常摆烂,刚开始的宏大构想与蓝图梦境被现实的划水击碎,开学后继续维持摆烂并更甚,开创了周末不间断熬夜的先河(臭毛病一直维持大学的寒暑假),然后还继续研究 Vim 配置(因此更无心学习),接着就是现实最为沉痛的打击——给了四校和分班考两大滑铁卢,但暑假依旧不知悔改,并在开学后继续挥霍时间。

而这时候还有几天就要国庆假期了,当时的我也要规划些国庆假要做些什么了,以及国庆假期后面就是一阶段考试了。因此我当时的心境就不难解读了,就是一种破罐子破摔、爱咋地咋地的萎靡态度。

然而即便是现在我都有点难以置信,就跟天意大手降临一般:

2023.9.24

最后我说「导致我成绩几乎一落?丈」,这在高二末确实如此。然而高三发生了奇迹般的变化,我自认为高三是最摆的一年,但即使是最差的成绩也比高一高二最好的成绩好。就在撰文两周后的一阶考,年排比高一高二最高的级部排还高。真让人感叹。

一年后转录的时候,当时已经坐在南大宿舍的我看到这句话感慨万千,写下来上面这句话。一阶考后我的心情也发生了变化,从「无望」变为了「不配得感」,而这种不配得感在后面模考(甚至越来越好)逐渐褪去。我尚未认识到我的位置的时候,定位就发生了突变。

当然,成绩转好另一个「负面因素」大概就是我更「心安理得」地摆烂了。虽然说感觉要是烂了其实也是自暴自弃不会有啥改变的,例如说我上面写得这么颓废,那国庆干了啥尝试改变?这在2022 的年终回顾《新鬼烦冤旧鬼哭》中有所记载:

然后国庆假期安排了许多事情,结果一项没完成。四号下午本打算拖着疲惫的身躯回学校补作业,心里期盼着要是网课该多好啊(不过当时感觉不大可能),然后就收到通知要网课了,然后又爽摆了几天。

当然国庆还是遵守了约定,将上面计划的内容写成了笔记。已被转录为《2022 年国庆小记》

随后就是「为深中庆生篇」的《截距式方程相关结论改进》,这也应该是我高中最后一篇笔记了。

最后一篇在空间写的长文,也便是2022 的年终回顾《新鬼烦冤旧鬼哭》了。再然后我的 QQ 空间,就恢复到了它原本的功能,像朋友圈一样简单写点自己的心情、感想。

回看这小节的标题「笔记『大业』」,此时旧有的笔记体系已经在无止尽的摆烂中宣告崩溃,后面已经基本上就是我随性而发的内容了。高三上学期刚开学的时候第一周、第三周记了点笔记,但也加起来不到两百行,再然后第五周写了《极限初探》,也给它彻底盖上棺材板了,后面再无一个新文件。

但在高三迫切的需求面前,我对这个体系进行了迅速的「改革」,形成了高三时期的「后笔记体系」。不过这部分暂时先按下不表,按时间顺序继续讲述。

AutoHotkey

然后网课开始后就开始划水了,这段时间投身 AutoHotkey,无心学习与钻研……

上面的「胡思乱想」中介绍了我网课期间干了什么,主要是在投身 AutoHotkey。这一点在返校后的说说也有印证:

2022.4.7

思前想后,还是发出来比较好,毕竟算得上是网课(划水)最大成果。

https://github.com/pilgrimlyieu/AutoHotkey-Script/tree/master/OCRC

上面的链接其实没法直接打开了,因为我后面对脚本的目录结构进行了重构。不过可以看出,这段时间最为重要的成果便是 OCRC,影响之大,让我在高考后还为它写了一篇博文——《AutoHotkey 脚本 OCRC 介绍》

跟二番战(或者三番战)Vim 类似,我印象中在投入 AutoHotkey 之前我就已经有过尝试了,不过当时很快就停止了。而后面网课期间再接触时一发不可收拾。

当时其实想学很多,我看建立了挺多目录,我印象中也有弄过 TikZ 之类的东西。但大多没开始或很快废弃了,最后一家独大的却是 AutoHotkey。

现在还能找到一个 2022.3.13 的 Test.ahk 脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
; everyting 搜索
EverytingPath := "D:/Program Files/Everything/Everything.exe"
#f::
	send ^c
	run %EverytingPath%
	#IfWinActive Everything
	sleep 300
	send ^v
	clipboard := ""
	#IfWinActive
return

; 课表
:*C0:ctb::
	run C:\Users\Administrator\Desktop\课表.png, , Max
	#IfWinActive ahk_exe D:\Program Files\Honeyview\Honeyview.exe
	KeyWait q, DT5
	WinClose ahk_exe D:\Program Files\Honeyview\Honeyview.exe, , 0
	#IfWinActive
return

; 时间表
:*C0:ttb::
	run C:\Users\Administrator\Desktop\时间表.png, , Max
	#IfWinActive ahk_exe D:\Program Files\Honeyview\Honeyview.exe
	KeyWait q, DT5
	WinClose ahk_exe D:\Program Files\Honeyview\Honeyview.exe, , 0
	#IfWinActive
return

这个 Win + F 还比较简单,是最早一批映射了。还有就是一些当时的安排,可惜现在找不到了。

翻阅旧资料的时候我还注意到一个目录叫 Board,里面只有 2022 年 2 月 23、23 两天即为简短的记录,但是在介绍说说了这是「将心之所想所念,所乐所愁留到这里」的地方,还取了个名字「笏」。当时学文言文记字词时看到了这个字的释义「古代臣朝见君时所执的狭长板子」,有「板」,于是取了这个字。

看来这便是我最早的记事板了,虽然说没写几个字就结束了。但我觉得很有意义,因为思路是一脉相承的,我现在的记事板实际上就是对当时想法的实现。因此这篇文章发布后,我也要将记事板改称为「笏」了,只不过为了区别,现在的记事板叫「今笏」。

最初我可能只是要一些自动化脚本程序?因此抄过一些 Python 脚本,如(括号内时最后更新时间):

  • ReadBigFile.py(2.25):用以读大文件特定部分。当时似乎写了个东西挂了好几天,生成了很大的种子文件,然后再尝试读其中的内容。不过现在看来其实没必要,才几 G,裸 Vim 可以打开。
  • UpdatePYW.py(3.5):循环间隔将脚本更新为 .pyw 文件。似乎是因为 .pyw 文件可以不打开命令提示符而执行。
  • StartUp.py(3.6):开机时自动执行,会自动登录 TIM、微信和腾讯会议。
  • ShutDown.py(3.6):每天定时关机提示,允许一定的缓冲。我看了一下里面设置,居然是十一点……
  • Words2Anki.py(3.18):就是上面说的英文单词表转换。当时发挺多个词表(一阵子发一张),我首先将单词表分区截图,然后用 ABBYY 自动化任务,再用这个脚本。具体使用情况已经遗忘了,但似乎效果不错?里面还有历史记录,不过就看日期似乎就 3.3 和 3.18。

然后我后面可能对其中一些脚本并不满意,感觉不够好,也可能是感觉写起来没那么舒服。总之,大概是同一时期我(再次)接触了 AutoHotkey。

根据现存资料的元数据,我在 3.4 晚上快十一点创建了 WordsCorrect 目录,然后将知名的 AutoCorrect.ahk 放了进去。而这个文件创建时间是同一天下午快五点。

然后我便开始了对 AutoHotkey 的研究。现存的旧资料下有这些目录(括号内是创建时间):

  • Anki(3.5 上午十一点出头):里面有 Cloze.ahk,看样子是非常早期的 Anki 打 Cloze 脚本。
  • TextReplace(3.5 晚上六点半):空,推测目的是弄一个自己的 AutoCorrect。
  • BaiduOCR(3.5 晚上七点出头):OCRC 前身,基础代码不是我写的,而是从别的代码改的,直到面目全非。
  • AutoHotkeyTools(3.6 上午快十二点):里面有一个十二点半左右最后修改的 Win.ahk,看内容只是为了获取活动窗口的进程路径,应该是用以调试的脚本。
  • Translate(3.6 下午快三点):似乎是一个划词翻译的东西,丢了库没法运行。
  • Translation-Terminator(3.6 下午快五点):非常大,有 200+M(主要是打包了一个 Chrome),在 GitHub 上依旧有更新。类似的,也是划词翻译。
  • SmartTranslator(3.6 下午快五点半):也是划词翻译,可以打开但用不了翻译服务。

OCRC 的前身也就是古早的 BaiduOCR 中有这样的说明:

1
2
3
4
; Adapted from https://www.autoahk.com/archives/35526
; Thank https://www.autohotkey.com/boards/viewtopic.php?t=86814&p=381493#
; Thank https://github.com/iseahound/Vis2
; Thank https://wyagd001.github.io/zh-cn/docs/AutoHotkey.htm

这也便是我参考的资料了。

我从这里第一次接触了 API 调用,我按照说明领取了百度 OCR 的权益,至今仍在使用。挺不错的,这也是我为数不多的对百度的正面评价了。

噢我想起来了,一开始也许是想网课的时候提高效率,于是了解到了 Quicker(印象中在此之前也用过,但后面逐步荒废)。然后里面有一个 OCR 工具就支持百度 OCR,我第一次应该是从这里知道的。

然后我忘了对其有了什么不满,随后在找到了 BaiduOCR,紧接着后面就浮现了自己魔改念头。

BaiduOCR 这个我记得之前是自己做了一个截图工具,然后好像我体验不是太好,似乎有点卡等。

于是我就想,我自己就有个截图工具(Snipaste),能不能复用呢?

于是我在没多少代码经验、AutoHotkey 都没怎么学过的情况下,开始看起了其他人的项目代码。

当然要读肯定也是读不懂的,当时也没有 AI,只能凭借关键词以及搜索大致猜测我要在哪里「动刀」。

结果真的给我折腾成功了,我真的就将截图工具换成了自己的。然后根据上面的链接,我大概也是第一次了解到 Base64 什么的概念。

然后就是进一步改进,例如说我后面想把 Mathpix,也就是公式识别的 OCR 也集成进去,这是我的初衷之一。代码后面是改得面目全非,除了调库的部分代码我看不懂基本没动(但有删)外,其他每一行我都改过。

现在想来也确实挺不可思议的,没有 AI,也没什么 LSP 辅助,就纯读代码、看文档(不只是 AutoHotkey 的,还有百度、Mathpix 等的 API 文档)、网上搜索,竟然就发展成为了我的第一个项目——OCRC。而在我高考后也将其记录成了博文(当然主要是 OCRC 的介绍就是了):AutoHotkey 脚本 OCRC 介绍

感慨之余再去看了眼 OCRC,发现 README 居然还写了不少内容,我摘录部分(引文是我现在的批注):

OCRC 的念头及百度 OCR 部分起源于 Quicker 动作 截图OCR。由于 Quicker 是付费的(虽然可以免费使用),就高仿做了一个。

因为 Quicker 本身存在付费增值服务,尽管我可免费用这个动作、尽管使用上没什么限制,我依旧是放弃了使用,而是转而自己改写。唉,哎。

这个动作更美观一点。比起 OCRC 多了行数(暂时想不到解决方法)、翻译、纠错、保存、文字大爆炸、内容审核等功能。

实际上纠错、内容审核也可以做,只是百度给的 API 是总量限制的,再加上用处不大,也就没有弄了。翻译是很重要的功能,以后肯定要加入的。

高考后加入了,不过用的只是 Google 的免费 API,效果一般般吧。

OCRC 的 Mathpix 部分起源于 GitHub 项目 img2latex,当时百度部分已基本完成,就高仿嵌了一个。

现在看居然已经 Archived 了,就在当年十一月。

也是更美观一点。还支持剪贴板图片显示、公式渲染等功能。

剪贴板显示的功能应该不难,但我觉得没什么必要。公式渲染的功能不错,但是可能太困难了,再加上公式一大显示不清晰,就没有这个打算。

……

OCRC 的百度 OCR 部分是基于 AutoAHK 论坛的 AHK 实现文字识别(OCR) —— 离线与在线4种方法总结 这篇文章改造的。不过已经改得面目全非,已然没了最初的模样。(截图工具内自带,如有需要可自行参考并加入)

……

知名问答平台。

更多的没找到了。

这里是在致谢 Stack Overflow,附图一张 4.1~4.3 的必应搜索记录。

……

AutoHotkey 官方论坛可以说是对我帮助第二大的了。

例如 How to change snapshots in clipboard to base64 directly?

这个问题是 AutoHotkey 论坛第一个对我有帮助的问题。它最终形成了 Common.ahk 中的 Img2Base 函数。

……

印象中在此之前接触过一两次 AutoHotkey,但都只进行了简单的操作便放弃了。而这次再次接触后却一发不可收拾,我想也有网课的原因。虽然有点后悔,毕竟很快我就要开学,就要大寄了,不过好在摆脱了摆烂的深渊(虽然是从一个深渊到另一个深渊)。

AutoHotkey 这个语言我可谓是又爱又恨。爱它的简洁、易用,又恨它的不规范。

……

还有令我感慨万分的就是我写的第一个类(Mathpix.ahk),用的不是编程路上第一个语言 Python,不是我觉得很优雅的 JavaScript,而是这个「不伦不类」的 AutoHotkey。

不管怎样,AutoHotkey 都在我的编程路上留下了不可磨灭的印记,是一座值得仰望的丰碑。将来在这条路能否走得更远,就看返校后的我了😢。

看起来有点好笑,写一个类原来都值得纪念吗。但确实,我就是这样一步一步走过来的。至今不到四年而已。

「在这条路能否走得更远」这个问题,我现在无法给个最终的结论,因为我依旧在这条路上走,依旧是要抛给未来的我。但我可以非常庆幸地没有在现在给出一个确切的否定答案。

Vim 与 UltiSnips

Vim

如上面所言,回校后我从 AutoHotkey 转而投身于 Vim 了。当然这是一个逐渐转移的过程。

翻看旧记录的时候,我意外地发现了一个 2021.9.19 的笔记,里面明确写了是「Vim 初识」。那这基本可以断定了,我第一次用 Vim 实际上是在这一天。

这个文件内容挺少,就简单记录了一下 Vimtutor 学到的东西,就到第五节文本编辑添加就没了。现在看来其实有点傻,毕竟我后面学会、熟练也不是靠记下来的,而是靠上手使用的(也正因此其实我做事情不太喜欢记笔记什么的)。

不过也正是因为这个文件的存在,我才能确定我第一次使用 Vim 的时间。

当然,第一次上手很快就遇到挫折了。首先是我感觉这套模式非常「反人类」,第一次接触的时候真的是颠覆了我对「编辑」这件事情的认知。现在自然是稀疏平常,但对当时的我冲击力还是不小的。还有一个原因就是,默认的 Windows gVim 是真的丑啊。没眼看。

我找了一个 22 年开的 issue 录制的动图,当时是没加载配置,显示了默认情况下的 gVim:

于是我很快就放弃了 Vim。我当时尝试接触 Vim 就是为了 UltiSnips,失败后就转向了 VS Code HyperSnips。

照理来说,这些时间我已经「养好」了 HyperSnips 配置,就应该老老实实用着吧?我在回校前也在心里叮嘱自己,这一套已经弄好了,后面就收心好好用吧,结果就是:

2023.4.17

然而根据经验定理,不想做的事优先级最高,「要」与「急」优先级最低,此外甚至评不上优先级。

这是一篇摆记的记录。既然我不想折腾 Vim 配置,那就是想!就是第一等要事!

然后我就再度学习 Vim,不知怎得,这一次竟就一发不可收拾了,然后就彻底染上了 Vim,用不了其他编辑模式了。现在 Windows 和 WSL 都装了 Vim 与 Neovim,VS Code 有 Neovim 插件,JetBrains 家的装了 Vim 插件,前不久用的 Zed 也启用了 Vim。有些地方不得不用 nano 还特别难受、别扭。

其实在开始我就有了解过 Neovim,不过最终还是选择了 Vim。原因不太清楚了,可能是因为当时 Neovim 还不够稳定,负面评价还比较多,因此就选择了 Vim。

不过到大学以后就也开始接触 Neovim,然后就真香了。但我大学以后没高中那股折腾劲了,因此配置也就简单写了一点,懒得将我最为核心的部分迁移过去——毕竟研究了很久,也没啥记录,靠的是当时的经验,早忘了不熟了)——因此现在就是属于 Vim 与 Neovim 共存的状态。

像我现在写这篇博文还是在 Windows gVim 中,然后 Windows 日常看代码也会用 gVim,毕竟有 AutoHotkey 配置的快捷键,从资源管理器启动很方便。但终端可能就会选用 Neovim 了。像 WSL 写笔记也依旧是 Vim,写代码一般是 Neovim,其他什么的就随缘了,看看按 v 还是按 n

有关 Vim 与 UltiSnips 的配置我就有点不想再多说了,因为高中后这部分依旧有变化,想要追踪有点太麻烦了,而且 UltiSnips 部分有相当一些在 HyperSnips 部分已经提及了。

vimrc 仓库(我的 Vim 配置仓库)第一次提交看,是在 6 月,只是这时候其实已经配置不少东西了。

不过虽然不能翻阅变更记录来说明,但看看第一次提交时的配置也不是不行。

首先自然是 _vimrc 文件了,也是其他平台的 .vimrc(我一直挺不爽这个的)。

一堆 set 就不提了,摸索出来的没啥好说的。看字体,当时就是 Microsoft YaHei Mono + JetBrains Mono 了,这个组合一直延续至今,只是 JetBrains Mono 换成了 JetBrains Mono Nerd Mono,用了支持 Nerd Fonts 的变体。

Vim 插件

有点意思的是后面的插件列表,用的插件管理器是现在还在用的(仅限 Vim)vim-plug。排行第一的是 joshdick/onedark.vim,也就是说当时我还在用 One Dark 主题,而在后面就全面转向 Gruvbox 了。

这个变化 blame 一下可以发现,发生在 7.10:Switch colorscheme from joshdick/onedark.vim to morhetz/gruvbox,已经快用四年了,时间应该是 One Dark 的两倍以上了吧。

列一下插件列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
call plug#begin("~/vimfiles/plugged")
" Plug {{{1
Plug 'joshdick/onedark.vim'
Plug 'yianwillis/vimcdoc'
Plug 'scrooloose/nerdtree'
Plug 'Xuyuanp/nerdtree-git-plugin'
Plug 'itchyny/lightline.vim'
Plug 'mhinz/vim-startify'
Plug 'neoclide/coc.nvim', {'branch': 'release'}
Plug 'dense-analysis/ale'
Plug 'SirVer/ultisnips'
Plug 'Yggdroot/LeaderF'
Plug 'tpope/vim-fugitive'
Plug 'tpope/vim-commentary'
Plug 'tpope/vim-surround'
Plug 'tpope/vim-repeat'
Plug 'easymotion/vim-easymotion'
Plug 'mg979/vim-visual-multi'
Plug 'Yggdroot/indentLine'
Plug 'luochen1990/rainbow'
Plug 'lervag/vimtex', { 'for': ['tex', 'markdown', 'vim-plug']}
Plug 'godlygeek/tabular'
Plug 'preservim/vim-markdown', { 'for': 'markdown'}
Plug 'ferrine/md-img-paste.vim', { 'for': 'markdown'}
" Plug 'python-mode/python-mode', { 'for': 'python', 'branch': 'develop' }
" }}}1
call plug#end()

一共 23 个插件,其中一个长期禁用。看了一眼现在是 22 个插件(Windows gVim,WSL 上是 18 个),居然数量上对上了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Plug 'joshdick/onedark.vim'
Plug 'gruvbox-community/gruvbox'
Plug 'yianwillis/vimcdoc'
Plug 'scrooloose/nerdtree'
Plug 'Xuyuanp/nerdtree-git-plugin'
Plug 'itchyny/lightline.vim'
Plug 'mhinz/vim-startify'
Plug 'neoclide/coc.nvim', {'branch': 'release'}
Plug 'dense-analysis/ale'
Plug 'mengelbrecht/lightline-bufferline'
Plug 'junegunn/vim-easy-align'
Plug 'neoclide/coc.nvim',             { 'branch': 'release' }
Plug 'SirVer/ultisnips'
Plug 'Yggdroot/LeaderF'
Plug 'tpope/vim-fugitive'
Plug 'tpope/vim-commentary'
Plug 'tpope/vim-surround'
Plug 'tpope/vim-repeat'
Plug 'pilgrimlyieu/vim-surround'
Plug 'easymotion/vim-easymotion'
Plug 'ZSaberLv0/vim-easymotion-chs'
Plug 'mg979/vim-visual-multi'
Plug 'Yggdroot/indentLine'
Plug 'luochen1990/rainbow'
Plug 'lervag/vimtex', { 'for': ['tex', 'markdown', 'vim-plug']}
Plug 'godlygeek/tabular'
Plug 'preservim/vim-markdown', { 'for': 'markdown'}
Plug 'ferrine/md-img-paste.vim', { 'for': 'markdown'}
Plug 'lervag/vimtex'
Plug 'pilgrimlyieu/md-img-paste.vim', { 'for': 'markdown' }
Plug 'mzlogin/vim-markdown-toc',      { 'for': 'markdown'}
Plug 'rrethy/vim-hexokinase',         { 'do': 'make hexokinase' }
Plug 'vim-autoformat/vim-autoformat'
Plug 'github/copilot.vim'

主题替换就不说了,然后首先少掉的就是 scrooloose/nerdtree 及其附属插件,换掉的原因似乎是因为维护并不积极,此外好像听说过性能问题什么的?我后面换成了 coc-explorer,但真要说的话,其实我也没用过……还不太熟悉这种方式,尤其还是在 Windows 中。像在 WSL 我 Neovim 就会用上一点了。

然后是 mhinz/vim-startify。一开始也是为了炫酷装了一个,但其实自己也没配什么。然后后面意识到其实没有用,因为我使用最多的场景就是直接选中文件快捷键打开 gVim,根本见不到开始界面。要么就是直接打开一个空的,此时就是为了临时记录点东西,更用不上了。而一样是在 WSL 的 Neovim 就依旧有,因为用了 Layzvim,默认就有,并不干扰我,我也懒得关。

紧接着的是 dense-analysis/ale,这个好像是因为卡?移除应该是挺后面的事情了,毕竟我看配置开关中还留着呢。

vim-surround 还在,只是以我的 fork 形式存在了。这是因为有一个我想要的 PR 没有合并,因此我 fork 了仓库(方便管控),pick 了他的修改。

类似的用了我自己的 fork 的还有 md-img-paste.vim。根据修改已经忘了是为啥了,而且我后面其实也用得很少了。

Yggdroot/indentLine 也没了,不过同一作者的 LeaderF 还在。这是因为仓库 archived 了,而且我用原生功能实现了:

1
2
3
set list
set listchars=tab:!>,trail:·,leadmultispace:¦···
autocmd OptionSet shiftwidth execute 'setlocal listchars=tab:!>,trail:·,leadmultispace:¦' . repeat('·', shiftwidth() - 1)

autocmd 主要是为了支持修改选项的时候也可以变更,这是后面遇到的时候添加的。

godlygeek/tabular 说实话我都没多少印象了,原来是一个对齐文本的,我没什么使用它的记忆。后面给 junegunn/vim-easy-align 取代。

最后就是 preservim/vim-markdown。好像就是我发现没它也行,而且有严重的性能问题。

再然后就是新增的了。

有一个 mengelbrecht/lightline-bufferline,一个 itchyny/lightline.vim 的插件,用以在上面多一个 bufferline。

然后是 ZSaberLv0/vim-easymotion-chs,则是 easymotion/vim-easymotion 的插件,用以让 easymotion 支持中文拼音。不过思索了一下我好像其实也没咋用过。

mzlogin/vim-markdown-toc 用来给 Markdown 文件手动生成一个目录 TOC。其实也没咋用过……

RRethy/vim-hexokinase 则是显示文本中的颜色。挺好用的,不过一些词会有意外的显示,例如说 Azure,但小问题了。安装时好像有一些小问题,我也忘了咋装上的了,需要 make

vim-autoformat/vim-autoformat 格式化,没什么使用记忆。

github/copilot.vim 就是新秀了,挺常用的。不过在 Vim 中主要就是编辑预测了,没怎么用过其他的(例如说 Neovim 里面的 zbirenbaum/copilot.lua 有 Chat 也没怎么用过),其实也不如在 VS Code 中体验好。

现在看来即便是一些现存的,也没啥价值了,尤其是现在不用 Vim 写代码,更是可以删掉一部分了。

我看一下这些现在启用的插件的使用情况:

  • gruvbox:主题。
  • vimcdoc:中文文档。
  • lightline.vim:状态栏(包含插件)。
  • vim-easy-align:对齐,用得稍少,主要是 Markdown 里面的表格对齐。
  • coc.nvim:嘛,用的有点多。但如果放弃掉 Vim 看代码的部分,其实可以删掉很多子插件,可能也就 MPE 等可以留下来了。
  • ultisnips:Snippets,没得说。
  • LeaderF:搜索,但怎么说呢,我用得不多……真要搜索似乎一般也是命令行自己 rg 了。
  • vim-fugitive:Git,用得太频繁了。主要是有时候要精细提交非常爽,VS Code 都做不到一块变更中只选一行增减,Vim 可以。
  • vim-commentary:注释,属于是融入生活的。
  • vim-repeat:同上。
  • vim-surround:同上。
  • vim-easymotion:还行,没有很熟练,就偶尔用用。
  • vim-visual-multi:多光标,这个没办法,即便我不是很熟全部功能其他编辑器都找不到替代,做得最好的也就 Zed 的 Vim 模式,但也稍微少点。
  • rainbow:应该是彩虹括号吧,好像其实不写代码也没啥用了。
  • vimtex:写 Markdown 用,LaTeX 也用,但我现在不写 LaTeX 了。
  • md-img-paste.vim:没用了。
  • vim-markdown-toc:这个 toc 我还弄了个 alias,打这一行的时候蹦出来三次,服了。现在完全没用。
  • vim-hexokinase:融入了。
  • vim-autoformat:没用。
  • copilot.vim:有用。

Vim 映射

Vim 的映射倒是比较持久。不过第一显著的大概是 Leader 键的变化,当初是 ,,而现在是 Space,只有 Local Leader 才是 ,

然后看最为核心的映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
inoremap jk <Esc>
inoremap kj <Esc>
nnoremap H ^
nnoremap L $
nnoremap U <C-r>
nnoremap ; :
map <C-j> <C-W>j
map <C-k> <C-W>k
map <C-h> <C-W>h
map <C-l> <C-W>l
nnoremap k gk
nnoremap gk k
nnoremap j gj
nnoremap gj j
nnoremap Z a<BS><Esc>
inoremap <silent><leader>w <C-o>:w<Cr>
inoremap <silent><leader>q <C-o>:wq<Cr>
inoremap <silent><leader><leader>q <C-o>:wq!<Cr>
nnoremap <silent><leader>w :w<Cr>
nnoremap <silent><leader>q :wq<Cr>
nnoremap <silent><leader><leader>q :wq!<Cr>
nnoremap <expr>0 col('.') == 1 ? '^': '0'

有啥区别吗?其实还是有点区别的,但比较短、一眼看得出含义的基本上沿用至今。

Vim 插件配置

当时大部分配置都是写在了 _vimrc 中,因此有 700+ 行。其中贡献最多的就是插件配置了。

这里就介绍其中两个,虽然第一个很难被称之为「插件」。

第一个叫 Tabout,这不是插件,就是一个自己的映射而已:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
" https://stackoverflow.com/questions/20038550/step-over-bracket-parenthesis-etc-with-tab-in-vim
" Tabout {{{1
let g:out_of_expression_quick_key = "<End>"

execute "imap " . g:out_of_expression_quick_key . " <C-r>=IncreaseColNumber()<CR>"
execute "imap " . g:out_of_expression_quick_key[0] . 'S-' . g:out_of_expression_quick_key[1:] . ' <C-r>=DecreaseColNumber()<CR>'

let s:delimiters_exp = '[\[\]{}()"' . "'" . '<>]'

function! IncreaseColNumber()
    let l:colnum = col('.')
    let l:line = getline('.')
    if l:line[col('.') - 1:l:colnum] =~# s:delimiters_exp
        return "\<Right>"
    endif
endfunction

function! DecreaseColNumber()
    let l:line = getline('.')
    if l:line[col('.') - 2] =~# s:delimiters_exp
        return "\<Left>"
    endif
endfunction
" }}}1

大概就是,可以按 Tab 跳出或跳入分界符。不过虽然叫 Tabout,但实际上用的是 End,原因嘛很快会提到。也正因如此,后面我重构配置的时候就改成 Endout 了。

还有一个是 UltiSnips,它的按键配置可是折磨了我好一阵子,甚至在上大学后初期还在折磨我。

当时的配置倒还人畜无害:

1
2
3
4
5
6
7
8
9
" UltiSnips {{{1
autocmd FileType snippets nnoremap <C-S-s> ggOclearsnippets<Cr><Esc>

let g:UltiSnipsExpandTrigger = '<C-A>'
let g:UltiSnipsJumpForwardTrigger = '<Tab>'
let g:UltiSnipsJumpBackwardTrigger = '<S-Tab>'
let g:UltiSnipsEditSplit = "vertical"
let g:UltiSnipsSnippetDirectories = ['Snips']
" }}}1

而下面则是现在的(局部):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let g:ulti_expand_or_jump_res = 0
function! Ulti_JumpOrExpand_and_getRes()
  call UltiSnips#ExpandSnippetOrJump()
  return g:ulti_expand_or_jump_res
endfunction

if &term =~ "xterm"
    let g:UltiSnipsSnippetDirectories  = ["~/.vim/Snips"]
else
    let g:UltiSnipsSnippetDirectories  = [$HOME . "/vimfiles/Snips"]
endif
let g:UltiSnipsEditSplit           = "vertical"
let g:UltiSnipsListSnippets        = "<C-S-A-F12>"
let g:UltiSnipsExpandTrigger       = "<A-F12>"
let g:UltiSnipsJumpBackwardTrigger = "<A-S-F12>"

inoremap <silent><A-F12>   <Cmd>exec (Ulti_JumpOrExpand_and_getRes() > 0) ? "" : endout#IncreaseColNumber()<Cr>
snoremap <silent><A-F12>   <Cmd>call UltiSnips#JumpForwards()<Cr>
inoremap <silent><C-A-F12> <Cmd>call UltiSnips#JumpForwards()<Cr>
snoremap <silent><C-A-F12> <Cmd>call UltiSnips#JumpForwards()<Cr>
nnoremap <silent><C-S-A-d> <Cmd>call UltiSnips#RefreshSnippets()<Cr>

UltiSnips 大概有几种非常重要的按键:

  1. 触发 snippets(并非所有 snippets 都是自动触发的)
  2. 跳转到下一个 tabstop
  3. 跳转到上一个 tabstop

而比较常见的配置其实就是将 1. 2. 都设置为 Tab,3. 设置为 Shift + Tab。这样的好处是非常简单,而且非常普遍。坏处就是,没法区别触发与跳转,而这在我实际使用过程中就困扰到我了。

实际上还是挺普遍的。例如说我上面就有出现过 dm 的 snippets,即行间公式。它在公式内部有一个 tabstop,但是在外部还有一个结束 tabstop。

因此默认情况下,如果这两个键相同,会优先选择展开而不是跳转。但有的时候这个就违背我的想法了。于是后面就加了另一个键,依旧是默认尝试展开,跳转次之,但额外加一个 Ctrl 可以强制选择跳转。

还有很多复杂的用例,我于是经过实验,形成了我最终的配置。

可以看到,我的「主键」MAINAlt + F12,其他所有键都是围绕主键来配置的:

我来简单讲一下吧(如果能够理解的话,毕竟已经不熟悉了):首先就是 MAIN 会尝试展开或跳转,如果成功的话就这样,失败的话会进行 Endout,即上面所说的 Tabout。

然后是上面提及过的,本来我看到 snoremap 都愣住了,后面才搞明白什么含义。

就是说有两种模式,xmap 是「可视模式」,也就是常说的 visual mode,而 smap 是「选择模式」,即 select mode,后者因为我禁用了鼠标选择等(selectmode)而不常见,尽管可以通过 gh 启动但我也没用过,而常见的 vmap 则是同时映射这两个模式。

而常见的选中后按 MAIN 进入插入模式输入触发 snippet,以此获得 ${VISUAL} 变量的特性,是可视模式,这个是默认由 g:UltiSnipsExpandTrigger 决定的,因此我下面没有整。

另一个则是触发 snippet 后,如果 tabstop 有默认值,那就会进入选择模式。这时候将其映射为跳转到下一个 tabstop。我既然当时选择这样做,那就一定有我的道理,应该吧。

默认的 Ctrl + MAIN 跳转下一个和 Shift + MAIN 跳回上一个就不说了。

只是,这似乎还有一个问题吧。主键为什么会是 Alt + F12 这样完全反人类的键呢?难道我有什么 M 倾向吗。

当然不是(难说),实际上我并不是按 Alt + F12,我按的是 CapsLock 即大写锁定键。

这是因为虽然 Tab 给 snippets 确实爽,而且算是非常通用的做法,但 Tab 本身也有非常重要的作用,例如说缩进等。而且其实很多插件也喜欢用 Tab,例如一些选词、跳转等。用了一阵子后我就很不爽了。

因此我就想要用别的键替代了。于是我将目光盯上了它下面的 CapsLock。正好我知道了可以直接 Shift + 字母键输入大写,而不用大写锁定,这个键就更显得鸡肋了。那不如将其利用利用。

不是?这怎么又扯到了 CapsLock 呢?不是在说 Alt + F12 吗?这两个有啥关系?

答案就是——其实没有任何关系。只是 Vim 你没法直接映射 CapsLock,因此你得迂回用 AutoHotkey 绕路一下,而 Alt + F12 就是我选择的一个「中转站」。实际上这种 Vim 没法映射的事情相当难搞,我在大学初的时候就遇到过比这个更加棘手的事情)。

至于 Alt + F12 的选择,其实也没啥特别的,目标就是要难按、少用。实际上也确实,除了少数几次故意的测试外,我从未按过这个组合键。

而且最开始其实并不是这样的。翻了一下高考后暑假重构 AutoHotkey 的 commit,在此之前其实是这样的:

1
2
CapsLock::SendInput ô
+CapsLock:: SendInput {Shift Down}ô{Shift Up}

这又是啥子?其实你按 Alt + T 就能打出「ô」了,因此这个符号其实就代表 Alt + T。当时的映射也是这样的:

1
2
3
4
let g:UltiSnipsExpandTrigger       = '<C-e>'
let g:UltiSnipsListSnippets        = '<S-Tab>'
let g:UltiSnipsJumpForwardTrigger  = 'ô'
let g:UltiSnipsJumpBackwardTrigger = '<S-ô>'

只不过后面才换成了 Alt + F12 这个更难按的键。跟 Alt + Q 用 Vim 打开选中的文件一样,没什么理由,就是随便选的然后固定下来了。

当然其实还是有一点理由的。Alt + Q 是因为 Q 是 QWERTY 键盘左上角第一个键,比较方便按,而测试就比较常用 A Z 这样的。T 则是因为在最上面中间,离两边的 Alt 都比较远,初衷也是为了难按。

这样看起来就结束了?实则没有那么简单。

最开始想的就是既然 CapsLock 这样好,那我其他地方也应该也可以留着(当然一直到现在它还是仅用于 UltiSnips)。为了最小化作用域,这个映射应该仅在 Vim 中生效。

这还不好办吗?ahk_class 或者 ahk_exe 不就行了?我一开始也是这样做的,后面就发现,不对,不行。

这是因为——Windows Terminal,噔噔噔噔。我在即将上大学的时候才逐渐接触了终端,我发现这个方法在终端中行不通。

这是因为不管怎么整,ahk_classahk_exe 这些窗口信息,反应的都是终端模拟器本身,而非里面的应用程序。

此外其实不仅仅是窗口的问题,还有模式的问题。我记得我甚至还苛刻地要求,不要在无关的模式触发这个映射。这个靠窗口属性可就无法反映了。

难道真的就无计可施了吗?当然不会,不然的话我现在[19]笔记都在 WSL Vim 中完成的是怎么做到的。

我注意到了 "Flying Vim" 现象。当终端退出 Vim 后,我注意到 Windows Terminal 的窗口标题变成了 "Thanks for flying Vim"。这是因为 Vim 退出以后需要恢复终端标题,但是 Vim 并不知道终端标题是什么,只好设置为了这个。

没错,窗口属性什么的我管不着,但我有窗口标题可以干预啊!我发现我可以在窗口标题中透露模式信息。下面是最早提交的代码(2022.7.29)

set titlestring=GVim\ Mode:\ %{mode()}\ \&\ Sever\ Name:\ %{v:servername}

随后我就能检测(2022.8.10)

1
2
3
#If WinActive("GVim Mode: (i|s|v|V)")

CapsLock::SendInput {Alt Down}t{Alt Up}

我想了一想,似乎输入模式是跟 J K 有关的。但在这里也无所谓了,都提一下,下面可能会说。

这已经是早期的代码了,再后面又进行了很多修改。例如说我感觉不用这么多冗余的内容,只要编辑的文件和模式就够了,于是:

1
2
set title
set titlestring=%t✏️%{mode()}

现在的检测如下:

#HotIf WinActive("^.*✏️[isvV]$") || (WinActive("ahk_exe WindowsTerminal.exe") && WinActive(" \| [\w-]*md$"))

这个其实近几天还改过……先不管后面的部分,看前面就可以对上了。「✏️」就是选定的锚点。

这样一来,我的 snippets 触发大业已成。

我现在的 vimrc 仓库中还有 UltiSnips 的相关补丁,不过内容除了一些 hack 外还有就是注释掉了我的部分更改。

上面说的「默认展开次跳转」,针对这个我提了一个 PR 加入反过来的选择:Add JumpOrExpand support by pilgrimlyieu · Pull Request #1542 · SirVer/ultisnips。算是我提的第一个比较符合常识的 PR 了?也大约是第一次接受其他人的 review。

其实我也没看 UltiSnips 代码,看不明白,而是就依葫芦画瓢,复制代码照着写。代码部分倒是不值一提,比较有意思的是我当时正好开始玩 WSL,玩的系统一直用到现在,而当时为了测试就上 WSL 运行了 tmux 测试,真是令人怀念啊:

我也是在作者那里第一次听说了「回归」regress 的概念,当时翻译过来还有点不明所以。而现在我已经了解到什么是「回归测试」了,甚至上学期《自动化测试》课程选的测试汇报就是「回归测试」。

配置部分还没结束。目前的 Vim 配置中有下面一些比较诡异的映射(节选):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-h> <C-w>h
nnoremap <C-l> <C-w>l
if has("gui_running")
    nnoremap <C-S-j> <C-w>-
    nnoremap <C-S-k> <C-w>+
    nnoremap <C-S-h> <C-w><
    nnoremap <C-S-l> <C-w>>
endif

inoremap <Esc>[lyieu;CL~ <Esc>

map <Esc>[lyieu;h~ <C-w><
map <Esc>[lyieu;j~ <C-w>-
map <Esc>[lyieu;k~ <C-w>+
map <Esc>[lyieu;l~ <C-w>>

map <Esc>q <A-q>
map <Esc>w <A-w>
map <Esc>e <A-e>
...

imap     <Esc>[24;3~       <A-F12>
...

这也是我接触终端后被给予的当头一棒。为了避免重复以及规律性,我的很多映射其实比较独特,而一些其实没法在 Windows Terminal 中被触发。

例如说终端没法区分 Ctrl + JCtrl + Shift + J,还有就是 Alt + 字母键等,以及我非常看重的 Alt + F12 的 snippets。

因此我在大学刚开始那段时间,沉浸于解决终端与 Vim 之间的冲突。最终才算是将 WSL 纳入了笔记系统。

不过说起来我都有点忘记了当初是为啥要在 WSL 写笔记了。似乎是因为 LaTeX 吧。LaTeX 在 WSL 编译速度更快,而 LaTeX 我也要用 Vim 写,因此就不得不研究了。而在后面顺带将 Markdown 笔记也一同迁移过去了。

经过我不懈的搜索、翻阅各种论坛、issue、讨论等,我做出来一个能用的方案。

首先那些可以直接打出来的就直接映射就是了,例如 Alt 什么的。然后就是区别不出来的,靠 Windows Terminal 来区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
    ...
    "actions":
    [
        {
            "command":
            {
                "action": "sendInput",
                "input": "\u001b[lyieu;h~"
            },
            "id": "User.sendInput.A61CEFB"
        },
        ...
    ],
    "keybindings":
    [
        {
            "id": "User.sendInput.A61CEFB",
            "keys": "ctrl+shift+h"
        },
        ...
    ],
    ...
}

这是 Ctrl + Shift + H 的例子,其他就不用多说了。

总之就是靠这种非常丑陋的方式,我解决了问题。你别管它优雅不优雅吧,IT WORKS!

该说不说真得感谢 Windows Terminal 的兼容性,这个近三年前弄的东西居然现在还能正常工作。

Anki + Markdown

中间插入了这么多有关我学习系统中「笔记」的部分,我也提过了我会将「笔记」制作成 Anki 卡片。但是这些笔记是 Markdown 格式的,而 Anki 卡片支持的是 HTML,这该如何是好?

我找到了 Markdown and KaTeX Support - AnkiWeb 这个插件,然后魔改入我的模板中,形成了 Markdown 版本的 Basic 与 Cloze 模板。

当时甚至还写过一篇文章、一份教程什么呢。只不过现在看比较丑陋,因此这里就不放出来了。对于更接近现在的版本,可以参考 Anki 支持 Markdown 的模板这篇博文。

新学习系统

然后是高三的新·学习系统。不过这段内容我已经没啥印象了,只能随便说说。

旧的系统就是我上面说的那样,可以用 VS Code 记笔记,然后再用 Anki 制卡学习。

然而这套体系无以为继,因为我的注意力都给夺走了,直到熟练了 Vim 后都没有用其写过笔记。但非常有意思的是,这个在我高三时撑不下去的模式,竟然在大学里复活了,并不断演进。

后面我就思考,这是为什么呢?我琢磨了一阵子,得出来的结论已经忘记了。但是我新冒出来的一个想法现在还有印象,那就是放弃笔记的中间产物。

也许是我觉得这套转换太过麻烦,也许还受到了 Neovim Everywhere 的影响[20],我在想能否跳过中间记录笔记的时间,直接在 Anki 制作,让笔记与卡片是一体两面。这样也节约了时间、精力。

现在已经不清楚这套系统工作情况如何了,但它确实是被造出来了。这也就是 Vark,OCRC 后的第二大「项目」,依旧是用 AutoHotkey 写的。Vark 其实不仅限于 Anki,对 Anki 特别支持的被称为 Vanki。

具体而言,它吸收了 Neovim Everywhere 的思想,想要做到 Vim Everywhere。实现方式其实也很简单粗暴,那就是按键,然后召唤出来一个 Vim 的小窗口。

因此,对应的 vimrc 配置也需要进行一定的精简,去除掉一些无用的东西,例如说状态栏什么的就是不必要的。而且应该默认编辑模式等等。

Vark 的思想其实非常不错,但我看了眼,高三最后一次更新是 2022.10.1。再然后就是高考后的暑假了,将其重构为了 v2 语法版本。

现在其实我已经有点忘了,当时为什么要自己手写一个简易的将 Markdown 转换为 HTML 的东西并用在 Vanki 中,明明我的 Markdown 模板已经成形了。

唔,好像有模模糊糊有点印象,因为我当时实现的限制,Markdown 模板的样式没不同模板好,对于不需要复杂的 Markdown,例如只有简单的加粗、下划线、高亮什么的就完全没必要。

可能也是因为这样,我选择了另一条路吧,手动将 Markdown 转换为简单的 HTML。但具体效果我也没什么印象了。

我忘了高三什么时候开学的,但按八月开始的话,整个高三我制的卡的数目是 8454 张,占总数的 52.83%,如果按笔记算的话,这个比例是 48.29%。这个比例还是相当夸张的,让我简单看看都制了些什么。

里面大部分应该都是英语的,也有相当一部分大约是批量导入的。简单看了一看,物化生大概基本是靠存量,然后生物在考试前不久有部分突击。

说实话现在我已经无法想象我当时是怎么制卡的了。以至于我有点难以判断 Vark 是否有用,我倾向于没用到什么,只是一个好玩的玩具罢了。

反思

高二上学期,我有一个「期中反思」,挺有意思的。时间节点是 21 年 11 月,期中考试后。摘录一些内容(有微调):

对 Anki 的反思

这半个学期,足以将 Anki 从我心中的神坛拉下。这并不是说我要弃用 Anki 或者说放弃 Anki-Markdown 联动学习方式,而是说我要对其进行修正。之前我将 Anki 封为圭臬,却忽略了正确使用他的方法。因此在此进行反思,更正错误,改进方法。同时也是反思的第一篇,希望能作为一个好的开头。

也是最后一篇,并非,倒数第二篇,只是最后一篇就三行而已。

里面列举了一些「违背原则」的例子,但其实多是有点「展示」的意思,没戳到重点,我自己也在里面这样说。

对 Markdown 的反思

无疑,Markdown 是极好的笔记工具。……

然而,高效的 Markdown 在我手上却没有那么高效。我认为数学笔记方面需要负 99.99 %\pu{99.99\%} 的责任。

看一下之前的数学笔记,大部分(指篇幅)都是一个结论+长长的证明过程。而这些结论又是几乎没什么用处的。而且由于篇幅大但笔记少,导致占用了 50 %\pu{50\%} 以上的时间却只能制出 1 %\pu{1\%} 左右的卡(一天制卡量最多大概为 600700600-700 张,其中数学卡片量在 1010 张左右)。可以说是极为不划算的。

再仔细划分其中的时间,我们可以发现占大头的时间并不是在 Markdown 的输入上——实际上一份笔记用 Markdown 进行纯输入(纯打字机器,不带思考)的时间是少于 5 min\pu{5min} 的——而是在画图上。无论是用 GeoGebra 画图还是 Mathematica 画图,所用的时间都是极为庞大的。

插播一点内容,现在已经基本不用 Mathematica 画图了,因为有学习成本,实在挤不出时间,因此大部分用 GeoGebra 画图。但耗时仍然是极大的。

除此之外还有重新推的时间,毕竟我带回来的只有思路和草稿纸,而非完整的过程。更别说还有在学校里推所用的时间了。当然我并不是说这是无意义的,只是没有这个必要去用大量宝贵的时间做这些用处不大的笔记。我认为这些笔记是必要的,但不是说要第一时间用 Markdown 制作成笔记,而是说等完成了带回来的全部笔记工作(我认为极为困难)再进行笔记,当然为了防止遗漏,可以先行用笔记本记下结论与大致思路。

这个直接导致的后果便是英语制卡的漏洞,除了开学制了一次卡后就基本再没进行大型的制卡活动,英语笔记记得随意,也没有翻过,因此英语知识点掌握程度低,导致了期中的滑铁卢。除此之外就是极多试卷被搁置,没能及时订正制卡,我估计在试卷袋里搁置等待笔记的最久远的应该有 22 个月以上的历史了。

这个问题其实一直困扰着我,然后到了大学我选择放飞自我——我可以说有那么一点学习的目的,但主要是我享受其中的乐趣。

对期中考试的反思

个人期中分数 分数 折分 班级排名 级部排名 年级排名
语文 * - 2121 - 552552
数学 * - 2828 - 457457
英语 * - 4343 - 11841184
物理 * - 66 - 5050
化学 * * 99 - 113113
生物 * * 3838 - 640640
总分 * * 2929 102102 -

后面还有对期中考试各科目的一些反思,内容特别有意思。里面写了一些科目最高分得主,其中一个年级最高分我当时就听个名字并不熟,结果后面成了我高三同学……

看了看当时这个成绩也是真逆天啊(* 打码具体分数)。真难想象我高三居然能「逆袭」:

可以看到其实基本上都是班级靠后位置的,这便是我高二的学习状态了。

对生活的反思

这个学期是 Anki-Markdown 联动的第一个学期,因此我对其抱有厚望,以为可以……

当然现实是血淋淋的残酷,撕开了我内心的伤疤。

「对生活的反思」写了我理想与现实之间的落差,省略号遮盖了部分内容,太羞耻了我不敢贴出来。

但是后面写的居然还挺振奋人心:

对生活的反思

可能一个班 3030 的人来评论班级水平层次是极为狂妄不堪的,但是我坚信我有水平进入班级前列、级部前列与年级前列。这次失败并不要紧,它暴露出我轻慢、好高骛远的缺陷,我应当改正这些缺点,继续前进。……

而且可以说居然一定程度上是一个「预言」。

剩下的就不多说了。实际上里面的反思内容与改进计划,我是——一个也没做。毕竟我就是这种地方不行啊。

翻看了一下成绩单,难怪我会稍微反思一下,毕竟高二上学期期中考试是我高中以来考得最差的一次,这还是在我高一下学期期末考了整个高一最好的一次、还开启了 Anki + Markdown 后,这怎能不让我感到挫败呢?

本来我还想说没事,后面还有更差的,毕竟我体感四校联考和分班考都挺差的。没想到这两次居然还可以?甚至四校还是我高二最高一次,看来记忆是有偏差。但分班考感觉是最差的这点没错,按级部排名虽然是高二第三(倒数第二),但按班级排名则直接来到了 35(这好像是分班后即在高三班级里面的排名)。但不管怎么样,整个高二成绩总体上是比高一还差的。

本来我突然有点迷糊了,高二上学期期末呢?然后才想起来,因为疫情延期了,然后就是延着延着就顺理成章取消了,哈哈哈。

上面还说的第二次反思,虽然内容不多,但一惊一乍的,就不放出来了。不过与其说是「反思」,不如说是反思的「预告」,结果后面压根没写。

高三

备忘录

高三很多东西没有留下来,唯二持续不间断的记录,其中一个就是「备忘录」。

备忘录开始的时间已不可考。备忘录其实就是在我手机的「备忘录」系统软件上,记录一些待办事项,提醒我周末去完成。

2023 年终回顾

高三时用手机备忘录待办记录了一些东西,往往是打算周末去做,却往往又在周日无功而返。这些待办名字都比较隐晦,有部分我甚至已经忘记了意思了。

非常遗憾的是,系统更新后顺序被打乱了,没法准确回忆或是确定时间。

显示共有 533 条待办,其中大部分都关闭了(不管是真的完成了,还是拖欠太久取消了),只有两条重要待办还开着:

  • [要] 暑期日程
  • [习] 打字 & 双拼

大概就是高考后,计划弄个安排不要荒废度日,此外还要练习打字和学习双拼吧。虽然暑假还是摆了,但在最后建立起了这个博客。同时拖延一阵子后双拼也学了,现在打字就是在用双拼。

当时我比较喜欢用「隐语」,不仅是现在这样,还有就是即便是正常的也会写得非常简略,因此就是有时候连自己都忘了啥意思。加上现在给打乱了没法定位,相当一部分已经不知道啥含义了。这点在其中一个叫「略详」的中有说,「对极其简略的内容,应在备注中附额外信息」。只能随便翻翻,看看有没有什么能说的、有意思的。

有一个「[告] 清卡运动」,记录了一些 Anki 搜索参数。按记录在 6.6 结束,评价是「较为失败」。

2022.10.10 -is:suspended 即未暂停的卡片破万,记在了里面庆祝。

还有一个「[警] 58 天未制卡(4.7 恢复)」,里面的备注是让我每天更新直到重新开始制卡。最终拖了两个月没制卡后终于恢复制卡了。大概是寒假后比较懒散?

3.28 完成了常规刷卡,里面写「不知时隔多少个月,终于完成一次常规刷卡……距高考 70 天。」。大概就是终于将所有卡清完了?

看到其中一条备忘录想到,高三好像是下学期的时候,带手机的同学要交给班主任设置什么应用锁还是什么的?就是不允许滥用。结果给我找到破解的方法了,具体不太记得了,但总之我依旧是爽用手机。结果高考后问老师,老师也不知道密码,但因为我有破解之法就不管了。

这又是为何。翻着翻着又翻到一个「[警] 32 天未制卡」,里面写「以 4 月 9 日作为标准,那么到今天制卡生物已经又过了 32 天了。引以为戒,距离高考 20+ 天。」,嗯……

2023.2.2 记录「积累改翻译」,然后「凭借多出来的 52 cards,2023.02.02 以 15009 carss 暂时突破 15000 大关」,错误是原备注就有的。

还有一个「高考禁网」的,大概就是因为我中考的时候,考完还上网跟人交流题目。决定高考的时候不能再看网上信息了,结果最终还是变成了「弃」,考完还是该聊聊,该看看。

2023.6.9 11:10,即高考最后一天上午,考完化学,临近中午,我记录破 16000 cards,到达 16002。

还有一个叫「好好休息」的,里面写「先从好好休息做起」,最终结果是「弃」,写「几个月了一次都没做到」,说的是周末回家次次熬夜熬很晚,结果又起很早,因此周末回校的时候都是精神恍惚的,常常要在这种状态补作业。

大概就是这些吧。其实还有很多有意思的,只不过有的我虽然还知道是啥意思但不能放出来,有的跟我的学习紧密相关,而相当一部分也不知何意了。

里面还有一条提醒我「回信」。高三五月的时候,临近高考,心更是躁动不安。而我在用 Anki 的过程中遇到了一些翻译问题,例如说格式、术语等,深感不适。因此提出了申请参与翻译。看了看时间,2023.5.8 正式加入。

印象中在此之前就参与过一点 AnkiDroid 的翻译,但体验一般,因为我提交的内容需要审核通过,但有审核权限的人好像已经沉寂许久了。而 Anki 这边我提交后就可以直接贡献翻译。

当然这种模式也造成了一些问题。因此翻译了一阵子后我就收到了一封信,应该也是除了小学的时候互发邮件外我第一次收到的真人来信了。大概是跟我沟通了一些翻译上的事情,随后表示后继有人,他似乎因没继续使用 Anki 而不打算进行后续翻译了。往来几封。

因此直到 2025.9,新名词的翻译基本上都是由我负责的。2025.9 后又发生了什么吗?其实没啥,只是一直没更新字段,我没得翻译而已……

再然后也有类似的事情重演,也有一个贡献者修改了一些翻译,正巧修改的名词也是我当初改的名词。于是我效仿前人,也给他发了一封信交流。

摆记

除了备忘录以外,还有「摆记」持续记录了我的高三,虽然跟备忘录贯穿整个高三不同,它只记录了我高三下学期,但以此了解我那时的心境则更为契合。

当然其实没什么掷地有声的东西,即便真有也很难在这里引述。所以就是简单挑一些看看罢了。

其中一个很重要的大概就是 AI 了。2022 年底 ChatGPT 横空出世的时候我其实也有所耳闻,不过当时因为注册条件略显苛刻,便没有尝试,但有所关注。

而第一个记录的则是 New Bing,当时可以申请试用好像:

2023.2.20

上周就收到邮件了,于是高兴地去试用。使用体验还行,偏主观方面回答比较不错,而逻辑(学科)方面倒不尽人意。如 Markdown 代码给我一堆空的 nested {},还有不生成完全,(1)(-1) -> ()(-),少打了 }(出现两次[总共两次测试])。还有一点极不满意就是动不动就不聊了,查了下是 MS 砍了,真可惜。还让它写了几个故事,虽然没写完,但观感还可以。另外 New Bing 更像一个人,不会老老实实遵循你的指令,如一个故事到一半,我让它继续写,它让我脑补,不写了,把我给气的,血压升高。聊天方面想要的是不会百依百顺的(但也不能处处顶嘴),至于实用则要一个听话的家伙。玩了大概一个多小时次数限额到了,但第二天还是用不了,可能时差?我也不知。

当时 LaTeX 还写得不伦不类呢,现在已经比我写得高到不知道哪里去了。

还有不久后的让 New Bing 写小说,下面这部分我都引用不知道多少次了:

2023.3.5

新必应现有 Creative(紫)、Balance(蓝)、Precise(绿)三种模式,周五看还是每轮六次对话,周六就变成八次了,当然远远不够。

……

总的来说 New Bing 还远不能胜任写小说的职业,不管是次数限制还是智能程度。对人物的情感,把握很差劲,理解也不到位,很难写出人与人间微妙的关系;对细节的捕捉,几乎没有,我很少看见什么细节;对情节的设置,有时有部分让人惊喜的创新,但大多时不尽人意,没有悬念、伏笔、突转之类的文学技巧;文字叙述上也并不理想,经常出现大段相同格式文字,AI 生成痕迹明显,对设定也就是直接带出,缺乏印证。但我对未来还是充满希望的,未来也许有机会欣赏「自己」创作的同人。

现在虽然说做不到完美符合我的感觉(这得靠脑机接口了,毕竟我没法准确描述出来我要什么),但已经可以做到段落层面的惊艳了。

2023.3.12

对于 New Bing,次数拓宽到 10 次了,但感觉又被砍了,问它点信息它说不知道,说数据到 2021(你又不是 ChatGPT,你可以联网啊!),而且之前问 ** 里的信息,虽然越到后面越瞎编,但好歹回答我了,现在则是这样,也提不起兴致就没再问了。

再到后面因限制,就逐渐没啥兴致玩耍了。真没想到三年之期已到后,会发展到现在这个地步,真的是太快了,太快了。

2023.3.19

第二主题是 ChatGPT。……,试了一堆免费虚拟手机全部失败,最后氪了 2.1 美元(坐地起价,几个月前还是不设下限,且 0.2 美元就能搞,现在是 2 美元起步,多的 0.1 是佣金还是多按的我也忘了[应该是佣金],还用了其中一半买)搞了个手机号,终于注册了个号,结果用不了…心态爆炸。且微软封锁了之前的方法,现在 New Bing 要翻了(翻了后也用不了,提示到限额),……

结果到后面就放开了,不用手机号了。AI 普及,真好啊。

2023.3.26

终于用 bing 写代码。清 cookies 再开 vpn 可用,然而实际测试才发现它代码并不好用,几个测试都无法通过。只有最后一个能用(加了标签),但可读性挺差。加之它心理脆弱也用得胆战心惊,而且搞完后也没修改适配,因为去摆其它了…

2023.4.10

New Bing 真是个心灵脆弱的宝宝!让它写代码,不能说规则什么的,要说什么目标;让它对比前后代码(实际上一点没改)就不聊了…

这里其实应该说的就是 Anki 的那部分代码了吧。

2023.4.23

哦还申了下 Bard,10 几分钟就过了,还行,但有点笨,而且只会讲英文。

这里的 Bard 就是最初的 Gemini。刚开始的 Bard 还只会英文、发布会翻车等,后面就赶上来了,成为了「御三家」的第三家。

2023.4.23

然后又想搞 SD 了,折腾了一会,发现配置不够,就又放弃了。大学再学一点搞吧,到时候技术应该会更成熟点,手的问题应该也能解决了。

SD 就是 Stable Diffusion,只能说当时做了一个非常正确的举动,因为我根本跑不了,别说当时了,现在也没辙。更何况刚开始的效果现在看来完全难以让人满意(当时其实焦点都还在六指问题呢,现在其实都已经到「感觉」的地步了)。

这也是我在摆记中最后一次提及 AI 相关了。

至于大学以后的 AI,早期文章/笔记有所提及,就不在此再长篇大论展开了。

结语

呀,总算是到了最后一部分。

真是不得了,这应该是我写着最吃力的一篇文章了,前前后后写了近十次,跨越数个月接力,终于是将这篇漫长、零散的回忆串到一个终点了。

虽然篇幅限制没法展开写很多东西,也放弃了记录某些内容,但长度依旧是远远超过了我的预想。而且因为跨度太大,两次写作之间可能间隔数月,连贯性也可以说是没有比这更差的了。

但不管怎么说,我总算是将我大学前的很多事情写了出来。大学后的内容其实我没谈及太多,因为对我影响最为深刻的就是我成年前的这十八年了,是我扎根与探索的阶段,后面只是走上了我早已铺就的道路罢了。

也算是了却了我高考后的一桩心愿了。高考后回到家我写了说说:

2023.6.9 19:23

呼,高考完了。考试的时候似乎完全没意识到这是这么重要的高考。到了结束才有一点怅然若失,高中生涯也宣告结束了。


还是难以置信,高考与高中都结束了。这个月应该会写点高三与高中的回忆,当然考得好可能更有动力写,考得差或一般的话也会尽量写一点。

结果暑假完全将这档事抛之脑后。最终算考得好还是怎样呢?我当时的说说依旧能反映我现在的心情:「虽然不太理想,但还算能接受。」不过可能还要补一句,如果当成是高中的结果,满意吗?那答案一定是「非常满意」。

即便是现在我已经在敲这段文本的时候依旧难以置信,我会走到今天这样的路途,这恐怕是我整个高中都未曾设想的。

当然我在高中的时候对未来也一无所知,正如我现在也感觉我的未来虚无缥缈,伸手没有触摸的实感。小学的时候最早想当警察,后面想当科学家,再到后面不知道自己想当什么了,甚至一直到现在——不知道自己会什么、能做什么。看起来还是有点退步了,连「我是谁」的问题都无法解答了。

虽然我常说自己的这些年来可以算得上是「枯燥」「无聊」,毕竟我没有一段标准意义上绚丽丰富的青春、没有与他人结下深厚的情谊、没有走出门外看看与听听现实的世界、没有专注学习打磨一门出类拔萃的技术、没有找到自己热爱且愿意余生为之奉献的事物等等,我有的只是一台电脑而已,但这台电脑唯一触及了我的内心,将其与外界连接起来交流沟通,在我封闭的小世界中洒下些许颜色。这条路独一无二,也许有类似,但绝不会有相同。

虽然贯穿着十八年的电脑不会是一台,甚至高一、高二的我都印象不深了,不确定是不是一台。但我还记得的是高三的那台电脑,配置很低,内存只有 4G,还给我强行升级成了 Windows 11,打开几个标签页就卡得不行。因此我没法拿它做什么高性能的事情,往往就是在网页中流连,在折腾配置中捣鼓。

此外还是一台一体机,虽说我从小到大对计算机硬件也没什么兴趣,但这也一定程度上阻断了我探索的方式,因此我更热衷于软件而非硬件。可以说我在大学前对硬件其实一窍不通,在大学时也兴致缺缺。

与此同时我对网络的了解也匮乏不已,尽管我在折腾这条路上不可避免地会遇到很多障碍,但很多时候都是搜索尝试解决,并没有勾起我深入了解的兴趣。跟硬件一样,我也只有在其他人口若悬河的时候避而不谈。但与硬件稍有不同的是,我对其还是抱有一定兴趣的,只是仍无时机学习。

这是一条奇怪而又曲折的道路,你很难说它对我的技能、知识、未来有什么直接的贡献,但我想全过程看完后你也绝对不会将其称为一无是处。

如果我有一台更好的电脑呢?也许我会拆解它了解计算机组成原理、也许我会学习很多网络知识、也许我会提前走上学习软件知识的正路,例如说学点编程语言学点算法打打竞赛什么的,这样我确实可能学到更多所谓的「有意义」「有用」的东西,但这一定不会是「现在的我」了。

因此也许我该感到庆幸,我更愿意把这一路看作「有限条件下形成的唯一路径」,而不是「错过了某个标准答案」。我真的很想给高二上学期期中失利的自己、给高二疫情时摆烂时的自己、给高三一阶段考前自暴自弃的自己,说一句「没问题的」。真的没问题,一切都是命中注定的,平行时空下的「我」会有其他的路途,但现在的我就是这些自己所做的事情汇集而成的。

我后面也一定会像高中时的自己那样迷茫无措,对未来感到有一点憧憬,但可能也有一些恐惧,时常会自我否定、觉得做的事情毫无价值甚至这个人的存在都毫无意义,但我也想提前给未来的自己说一句「没问题的」。最终回首过去,一切惊涛骇浪都不过成为了塑造沟壑的溪流,一切电闪雷鸣也不过成为了点缀夜空的星辰。


  1. 当然没到太过原始,访问比较频繁,只需要快速一个关键词就能地址栏访问了。因此清除浏览器记录也是我访问的一个重新开始。 ↩︎

  2. 不是「它」,是「他」。 ↩︎

  3. 我去,怎么印象中比较爽的片段都是红方。 ↩︎

  4. 这个的技能我记得是可以夜间烟雾人让它无法行动,类似绑匪,但同时它又不能被其他人行动,因此也有点保护的效果,但第二次被烟雾就窒息而亡。 ↩︎

  5. 查了一下,叫「藤魔」,这是「超能模式」里面的。牛仔是「驱魔人」,另一个模式「异度空间」里面的。其实「异度空间」这个模式似乎就没啥光辉战绩了,因为逐渐没咋玩了,但天煞、藤魔似乎还是有点。 ↩︎

  6. 《生死狙击》中。 ↩︎

  7. 可能也喜欢过绿色,因为是自然的代表色,不过大概是同时期的。初中以后不再说自己喜欢蓝色绿色,更喜欢黑、白、灰这样的颜色,或者说不能叫喜欢,而是「会说自己喜欢」,因为没有特点,是我衣服会选的颜色。但蓝色等这样的颜色依旧是会有特别的地位,只是「不再会说自己喜欢」。 ↩︎

  8. 更绷不住的大概是,现在我的私博也有自己的「生死狙击」。 ↩︎

  9. 当然,不算一些极少时刻玩个几小时,但只会玩一次的,即便是这种也是少之又少了现在。也不算重温登录一下。 ↩︎

  10. 当然,其实归根结底是我现在不玩游戏。或者说,不跟人玩游戏。要是我依旧沉迷游戏,估计那 Steam 还是离不开的。 ↩︎

  11. 不过其实我个人不怎么喜欢旅行就是了,或者说我不喜欢嘈杂的环境与过程的疲惫。 ↩︎

  12. 我是非深户 D 类。 ↩︎

  13. 似乎是高三的英语词表,从 Test 01 到 Test 28。后面翻了一翻文件,找到了一个 Words2Anki.fta,是当时用破解版 ABBYY 的 OCR 功能将词表转为文本格式,而步骤比较繁琐存了一个流程。 ↩︎

  14. 为什么要说基本上呢?因为当时的 Markdown 笔记模板就做不到,这个后面会涉及。 ↩︎

  15. 现在我没有公开这玩意,说明它已经失败了。不过我当时还是抱有这种想法的。 ↩︎

  16. 代表光标所在处。若有多个则代表 tabstop,序号依上下文而定。 ↩︎

  17. 笔记第一句话原话。 ↩︎

  18. 看了眼成绩单,确实如此,分班考生物 58 没及格,班级 45。但还有高手,物理 60+ 排名 49,我有点忘了高三班级有多少人了,但应该就倒数一二吧。 ↩︎

  19. 其实不能算现在了,因为现在都 AI 笔记了,我最多改改。 ↩︎

  20. 虽然我当时没用过 Neovim,但我听说过了 Firenvim,大受震撼。 ↩︎