怎么用yasnippent实现优雅的带括号的过程调用?

举个例子,假定一个snippet脚本是这样的:

# -*- mode: snippet -*-
#name: (car e)
#key: car
# --
(car $0)

你在任何一个位置输入car,都能够自动生成这样的形式:

(car |) 这里|代表光标

但是,当你需要调用一个自定义过程时,比如你想要的是:

(foo "hello")

你在任何一个位置输入fo,虽然emacs能够auto-complete "foo"这个符号,但是不会产生括号:

(foo |)

所以,每次你需要调用自己定义的过程时,都要先(|),然后再输入过程名。

换句话说,调用自定义过程和调用lisp内置过程是不一致的,这种不一致在写代码的时候会降低流畅性(这个在非s-exp的语言里没有问题,因为这些语言里每个语法形式都是字母开头)

想请教下,你们是怎么做的?

我的想法是:你可以在任何位置输入任何字符,比如 foo,然后下拉列表里可以选择 (foo ) 这样的形式。

(yasnippent 能处理这种情况吗?还是只能固定形式。。。)

多谢。

不如自己习惯一下lisp这种先打括号的方式?

不不不。

你看yasnippent默认的那些snippets,都是输入一个不打括号的key,然后生成打括号之后的code-snippent。

说明大部分人应该都不怎么适应先打括号的方式

喵喵喵?

写lisp还有不适应先打括号的吗??可能是你Clike写得太多

如果你是python程序员,你可以从ruby程序员的snippet里都自带end推断出ruby程序员大都不习惯写end么

都用没有括号的形式,大概是因为没有必要写这个括号…


从另一个方面想,假如什么字符都能触发自动包裹括号的snippet,那tab键就跟废了一样

那你的tab键不就变成括号键了么

我的意思是:

如果你对自定义过程打括号,但是对内置过程不打括号,会造成不一致。

除非你所有的形式都是先打括号(也就是不使用yasnippet的所有内置snippets)

1 个赞

你简直把 yasnippet 当作万灵丹了。

yasnippet 是用来辅助输入大片代码的,如果 car 也要模版,就过犹不及了。

定义 snippet 模版通常会取一个简短 key,模版多了,带来的是记忆负担。

函数固然有内置,然而 yasnippet 模版并没有内置。你定义了 foo.snippet 自然就可以 foo<TAB>,你没定义 bar.snippet 就 (bar |) 也不是什么大事,扯不上不一致。如果按照你的逻辑,应该把所有的函数都绑定一个快捷键,因为如果有的函数有快捷键,而有的没有,严重不一致

本来输入 fo|(fo|) 得到的补全项是不一样的。输入 fo| 希望补全的是变量,当你把大量函数模版添加进来,反而造成了干扰。

1 个赞

你找错了对象

应该用company的

company真的可以吗?

我现在的需求是:手打输入foo,要求完成列表里有(foo )这一项,选择它后在buff里显示(foo |)。

注意1:我输入的不是(foo,而是foo。

注意2:产生的snippet里光标位置在(foo |),而不是(foo )|

car为什么不要模板?

我看 @manateelazycat 的emacs-lisp-mode 里就有cons car cdr等内置函数。

https://github.com/manateelazycat/lazycat-emacs/tree/master/site-lisp/extensions/yasnippet/snippets/emacs-lisp-mode

另外,我输入foo,希望下拉列表里有一个(foo |) ,这个真的会带来记忆负担?

当然如果你还有其他foo*,比如:foo1 foo2 foo3,那么下拉框里会出现:

foo 
(foo)
foo1
(foo1)
foo2
(foo2)
foo3
(foo3)

然后你只需要选择一个就行了,比如选择(foo),就会出现(foo|)

你当然可以给任何函数添加模版。

如果函数名叫 foo,模版也叫 foo,当然不需要费多少脑细胞。但是如果模版叫 f 你就得好好想象它代表的是哪个函数了。

你确定上下翻页查找,会比直接输入 (foo 更便捷?

不是因为更便捷,而是为了和其他snippet的语法形式保持一致。

保持一致有什么好处?

好处就是:就是这一块就能机械化操作了。不然你写算法的时候,对于内置函数 or 自定义函数,还要去思考一下用哪种形式,破坏心流。

我觉得你犯了「XY问题」这只是简单的一个包裹一下sexp的行为,或许根本不需要yasnippet。

(defun wrap-last-sexp ()
  (interactive)
  (save-excursion
    (backward-sexp)
    (insert "("))
  (insert " ")
  (insert ")")
  (backward-char 1))
before

wrap-last-sexp|

after 

(wrap-last-sexp |)
1 个赞

你说的是 符合某正则时,snippet 按某种形式展开 的功能,yasnippet 不支持。

可以不用 yasnippet,就像最后一楼里提到的,写个函数来实现。就是得额外定义一组快捷键罢了。

eldoc的内容应该也自动的列出才好吧

谢谢,代码收了。

不过这个方法还是不机械,最好还是能有我说的那个办法,让所有语法形式保持一致。

未来看看能不能自己做一个。。。

这个东西必须用snippet来做,company不行。

因为snippet是可以嵌套的,比如:

(lambda (n) 
  (foo (bar 10|)))

bar完成之后TAB要跳到foo

(lambda (n) 
  (foo (bar 10)|))

这种嵌套只有yasnippet才能实现,如果你是用company填充的foo 和 bar,那么bar完成后TAB会跳到:

(lambda (n) 
  (foo (bar 10)))|

即:整个lambda结束了(因为lambda是一个snippet,而foo bar不是)。 而我们要的是(bar 10)之后继续写foo的第二个参数。

你要的是 projectional editor,GNU/Emacs 纯文本实现这个太蛋疼。

直接考虑用 Jetbrains 整一个也行。

MPS: The Domain-Specific Language Creator by JetBrains

1 个赞

yasnippet的内置snippets被单独打包,是不是说明大多数人并不用内置的snippets呢……

这个不一致很好解决,就是不用car这样的 snippet. 他也没有省按键是不是。

功能已实现:

:blush:

company-mode has company-transforms support. That’s what you can hack for this purpose.