yasnippet如何在eval部分使用$1?

在yasnippet中,`(some-code)`可以用来执行elisp。


于是我在web-mode里加了一个snippet:

`(save-excursion (move-beginning-of-line 1) (if (looking-at " *$") "console.log($1);" "class=\"$1\""))`

意图是:expand “cl” 这个snippet时,某一行是0或多个空格时,猜测是js代码,变成console.log(),否则猜测是html,变成class=""

现在的问题是:elisp的字符串里的$1被literally地输出了,并没有被yas解释为通常的$1(一个可输入位置的占位符)

可以在 expand-env 中完成复杂的逻辑:

# -*- coding: utf-8; mode: snippet -*-
# key: foo
# name: foo snippet
# expand-env: ((bar (do-something...)))
# --
`bar`

相当于:

(let ((bar (do-something...)))
  bar)

哦对了,要不是这里输出$1之前的部分会破坏(looking-at ...),本来是可以抄文档里这种的(yas-prefix是prefix argument):

<p>`(when yas-prefix "\n")`$0`(when yas-prefix "\n")`</p>

把判断重复一下就行


我改成了:

# -*- mode: snippet -*-
# name: cl
# key: cl
# expand-env: ((jjpandari-web-mode-in-js-p (save-excursion (move-beginning-of-line 1) (looking-at " *$"))))
# --
`(if jjpandari-web-mode-in-js-p "console.log(" "class=\"")`$1`(if jjpandari-web-mode-in-js-p ");" "\"")`

现在总是输出class="",也就是env总是nil,感觉yas eval env时是不是有什么坑。。

把 env 的代码放到 M-: 执行,看看是不是 nil。

期待是t的地方的确是t。。

当你在空行执行 (looking-at " *$") 的时候得到 t 没错,但是输入 cl 之后就应该是 nil 了,所以展开 snippet 的时候当然也是 nil。

这样才对吧:

(save-excursion
  (move-beginning-of-line 1)
  (looking-at "^[ \t]*cl[ \t]*$"))
1 个赞

正则是对了,但是用起来就是只出class,奇怪。。。

我觉得可以转换下思路:不要把整个处理逻辑都包到尖括号 `` 中,而应该把它们包装成函数。然后在编写 snippet 的时候调用,与 $1 占位符组合。伪代码就像这样:

function newFunc()
    do something
end function

-- snippet 调用函数
`newFunc()` $1

相比全部放到尖括号,包装成函数调用更加好看、易懂,而且允许更复杂的处理逻辑。

我个人并不懂 elisp,所以这里只是个建议。你可以朝这个方向找找看,看 yasnippet 是否支持读取 snippet 内自定义的函数。


我觉得你的困惑,主要原因是在 同一函数域 内,elisp 恐怕无法理解 $1 是什么鬼东西罢?换句话说,直接执行 message("$1") 这种脚本,任何语言恐怕都是原样照出 $1 。因为它们不在同一域内。

我试了一下,snippet 展开的时侯,会把 cl 给吃掉,所以你原先的正则是对的(虽然没有考虑 \t:sweat_smile:


Update

# -*- mode: snippet -*-
# name: cl (command)
# key: cl
# type: command
# --
(let ((in-js-p
       (save-excursion
         (move-beginning-of-line 1)
         (looking-at "^[ \t]*[ \t]*$"))))
  (yas-expand-snippet
   (if in-js-p
       (concat "console.log(\"$1\");")
     (concat "class=\"$1\""))))

expand-env 写法有问题,我改成 command。

1 个赞

上面给出的expand-env就是“每次展开snippet时eval一些代码把结果存在变量里”呀,和你说的用函数差不多的。


"$1"这个,我想了下,没有宏的语言,多半要eval了` `里面的内容然后和外面的字符串拼起来,再处理一下,替换$1之类的,这样我(if foo "bar$1" "baz$1")还是有可能得逞的。但是elisp写的yas多半是整个snippet喂给一个宏,扩展出来的代码再eval这样,于是等我输出bar$1的时候宏扩展已经结束了,当然就直接输出$1了。yas的源码里肯定能找到答案,我就胡吹一下,如果有道友有兴趣有精力看源码欢迎指正

然而楼上给的答案里yas-expand-snippet又是个函数(参数为string)而非宏,不懂。。

  • 如何实验发现会把cl吃掉?
  • expand-env不行是因为它存在bug么?
  1. 在 snippet 里打印一下当前行内容就知道了。其实仔细想想 snippet 工作方式,不就是删除替换吗。

  2. 是 expand-env 有问题,还是有其他因素,还得想个 programmatically 的方法来证明/重现。

啊,看到你楼上调用的 jjpandari-web-mode-in-js-p 了。

之前看过 yasnippet 文档,印象中感觉 expand-env 就只能传递变量?类似于 export envpath="foo" 这样的,毕竟名字包含 env 嘛。难不成真能调用自定义函数?

你可以检查一下,jjpandari-web-mode-in-js-p 返回值是不是固定的。

话说回来,也可以创建两个触发词都为 cl 的 snippet 嘛。顶多输入时多了个弹出框选择的步骤,显得没那么智能罢了。。。不值得在 yasnippet 这么老的插件上花费太多时间,个人感觉。

老不老不知道,还是很强大的,楼上已经解决啦。可不能说“也就多一步/一秒嘛”,每当我向别人展示emacs的编辑超能力的时候,他们也经常这么说。。我可是把别人移动光标的时间都省下来,接了更多的任务,还没找老板要奖金呢

刚才看了下 yasnippet 的 github 源,发现还在正常更新。貌似挺正常的,这么说 yasnippet 并不老……

可能是之前被 yasnippet 坑过吧,再加上有 ultisnips 的使用经历,导致我对 yasnippet 没啥好感,有点老旧不更新的错觉。我记得之前翻过 yasnippet 的自带 snippet,并没有发现令人眼前一亮的。可能学会 elisp 后功能会更强大一些吧,然而我并没有这个动力……

并没有发现 emacs 的 编辑超能力,至少在我蹲坑论坛的这段时间,发现绝大部分人遇到的问题都可以用一个方案解决:换用 Vim 编辑器。包括你这个问题。๑乛ェ乛๑ 没错,我偏向 Vim 党多一点。

两者也面临着同样的问题,可能别人不觉得你有超能力,是因为人家的关注点在别处。比如自动补全、debug、重构等等方面。任何事情都有优点缺点。

1 个赞