正当我要在新地方开始新工作时,我的 RSI 突然爆发了。
郑重声明,我很好,我知道这件事已经发生有一段时间了。无论如何,火势即将结束。我很幸运,我会没事的。但这仍然有点令人遗憾。
上次发生这种情况时,我主要通过写技术方面的东西来度过难关,但我想我需要能够再次编程。我知道我是一名 emacs 用户,但为此我一直在使用 Visual Studio 代码,因为特别是一个扩展: Cursorless 。
Cursorless 是一个与语音控制软件集成的插件,可让您用语音进行 AST 级别的代码编辑。这是来自未来的疯狂外星魔法。
我之前在博客上讨论过无光标,但这次我决定真正深入探讨它。上次我使用它时,除了在屏幕上移动之外,我实际上并没有使用它,但这次我将尝试将它用于所有用途。
我希望我能用它作为松弛和不和谐消息的输入方法。
最神奇的部分是无光标输入时目的地和目标的想法。目标是文档中的各个锚点,而目的地是相对于各个目标的位置。文档中的每个标记都被赋予一个带有颜色的字母的帽子。这些帽子充当锚点,让您根据位置、目的地和它们之间的路径发出命令。这是一个简单的例子。考虑这段代码:
function fetchBlog ( ) { fetch ( "https://xeiaso.net/blog.json" ) . then ( ( response ) => { if ( ! response . ok ) { throw new Error ( "Network response was not ok" ) ; } return response . json ( ) ; } ) . then ( ( data ) => console . log ( data ) ) . catch ( ( error ) => console . error ( "Error:" , error ) ) ; }
这是一个看起来相当标准的 JavaScript 函数。但是,无游标对所有代码都加了很多帽子,所以它可能看起来像这样:
这就是为什么你的编辑器充满了随机的视频伪影?
它们不是文物,而是目标!
再仔细看看那张照片:
帽子的各个字母上方都有颜色编码。位置告诉您名称,颜色告诉您如何消除歧义。例如,该单词function
将被称为green urge
,因为字母 u 上的帽子是绿色的。如果我出于某种原因想删除该词,或者想将其移到其他地方,我可以使用green urge
作为该操作的目标。
就其本身而言,这为您提供了一些非常强大的动作,并有效地让您可以进行语音 vim 动作。但是,这只是考虑您可以使用编辑器执行的简单操作。无游标的真正力量不仅来自于路径的概念(例如green urge past green bat
选择屏幕截图中的function fetchBlog
),而且来自无游标知道该语言的 AST 正在做什么的事实。这意味着您可以在整个函数中执行操作,例如删除它或将其移动到其他地方。作为示例,这里或该函数的 lambda 单独可视化(使用“可视化 lambda”命令):
这些 AST 单位也是目标。这意味着我可以做一些事情,比如选择定义的主体,然后对其进行处理。因此,如果我想将其重构为异步函数,重构就变得微不足道:
这很酷,但我认为我无法记住所有这些命令。
一段时间后,它们就像 Vim 命令一样成为第二天性。在过去的几天里,我一直强迫自己一遍又一遍地使用它,它开始成为第二天性。我有时会引入单独的命令,并将其构建成更大更好的东西。
当您开始利用 Cursorless 和 Talon 的全部功能编写自己的命令时,真正的魔力就会出现。在我刚刚向您展示的那个示例中,我有一个在函数定义之前插入"async "
的操作。这是代码:
[state] async <user.cursorless_destination>: user.cursorless_insert(cursorless_destination, "async")
您可以将 talon 命令分为两个基本部分:模式和捕获。模式是您所说的话语,捕获的是您想要从您所说的内容中提取的内容。在本例中,模式只是单词async
,而捕获是您要在之前插入单词async
目标。 <user.cursorless_destination>
捕获是一种特殊的捕获,可让您指定是否想要目标之前或之后的内容。当然,这只是一个非常简单的例子,它可以变得比这更复杂。
这是迄今为止我写过的最复杂的 Talon 规则:
(method|meth) <user.letter> [<user.go_pointer>] [<user.go_visibility>] <user.text> [over] [<user.go_visibility>] named <user.text> [over]: user.go_method(go_pointer or "", letter, go_visibility_1 or "public", text_1, go_visibility_2 or "public", text_2)
那里到底发生了什么事?
这看起来很多,但实际上非常简单。这允许您在 Go 中声明一个方法。在 Go 中,方法如下所示:
func ( reciever * Type ) MethodName ( ) { // function body here or something }
迂腐地说,Go 没有传统意义上的方法,它只有将结构体作为接收者的函数(读作:隐藏的第一个参数,但以特定于该结构体的命名空间的方式)。如果没有东西自动为您编写此内容,您将不得不这样说:
状态 Funk args 单词接收器 空格星锤类型 over go 右空格锤 方法名称 args 转到右括号 Enter
有很多话要说。但是,有了这个利爪规则,你就可以说:
meth r 引发了名为方法名称的类型
这还是很多话。
嗯,是的,你将无法克服这一点。但这至少更高效,也更有意义。我无法摆脱所有的单词,但我至少可以使它接近我在脑海中概念化的方式。
我想这是有道理的,但是你所说的提升是什么意思?我不认为 go 已经引发了类型,我知道它有指针,但没有“引发”。什么是筹集?
我很高兴你问了!这是我正在尝试的事情,试图找到一种不同的方式来解释 Go 中指针的概念。我认为 Go 语言的疏忽之一是指针使用了 C 风格的语法。具体来说,您可以使用*
将值从指针值降低到正常值,并使用&
将值从正常值提高到指针值。
由于我借此机会从根本上重新设计了 Go 的 Talon 绑定,因此我想尝试将指针值的语法统一为升高和降低的想法,以了解它如何使 Go 程序更容易理解。我不知道这是否是一个好主意,但你必须四处看看才能找到答案。
也许我们行业的某些部分实际上是好的。我真的希望我很快就能进入GitHub copilot 语音测试版,我想比较一下 Talon 的语音编码方式和 copilot 语音的方式。