Ultisnips是否支持多行正则触发?如何更新Ledger账本?
关于Ultisnips多行正则触发片段的问题
核心疑问
我想创建支持多行触发的Ultisnips正则片段,试了以下代码:
snippet 'hee\nhaa' "multi-line trigger" r my awesome result$0 endsnippet
在Vim中输入:
hee haa<TAB>
无法触发,换成\r也无效,想问这种多行正则触发是否可行?
选择Ultisnips的原因
我知道sed处理批量更新更简单,但要处理hledger.journal格式的账本文件,里面大量条目既有固定格式也有复杂格式:
- sed无法方便调用我编写的全局Python函数做计算
- sed很难精准定位需要更新的条目
- 用Ultisnips虽繁琐,但能逐行确认更新正确性,完全可控
实际需求与尝试
账本里几乎每一行的信息都要通过match.group()提取用于更新,我会用!p snip.rv = calculate(match.group(x), match.group(y))这类代码做计算,但不确定pre_expand能否获取snip.line-1或snip.line+2的匹配组,实现难度较大。
我认为带多行正则触发的Ultisnips是更新账本的最佳方案,哪怕过程繁琐。
原账本条目示例
2023-07-16 tfuel reward equity:stak_reward:tfuel:2023-07-16 -12 tfuel asset:tfuel:Liq:2023-07-16 12 tfuel
目标格式
2023-07-16 tfuel reward income:stak_reward:tfuel:2023-07-16 -0.427 eur equity:opening_balance:stak_reward:2023-07-16 -0.427 eur asset:tfuel:Liq:$1 12 tfuel @ 0.03555137 eur equity:conv
我尝试编写的片段
snippet '(\d{4}-\d{2}-\d{2}) tfuel reward\n.*equity.*\n.*tfuel' "migrate all tfuel reward" r ${1:`!p snip.rv = match.group(1)`} tfuel reward income:stak_reward:tfuel:$1 -${2:`!p snip.rv = calc_value(match.group(1), 'tfuel', 12)`} eur equity:opening_balance:stak_reward -$2 eur asset:tfuel:Liq:$1 12 tfuel @ `!p snip.rv = get_token_price_in_eur(match.group(1), 'tfuel')` eur equity:conv endsnippet
更复杂的条目包含可变资产和数量,需要提取不同行的多个匹配组。
解答
1. 多行正则触发的可行性
Ultisnips原生不支持多行正则触发,核心原因是它的触发机制默认仅检查光标所在行的内容,不会跨多行匹配正则表达式,所以你尝试的\n或\r都无效。
2. 替代方案:用Python代码实现多行匹配
虽然原生不支持,但可以借助Ultisnips的Python扩展能力实现类似效果,步骤如下:
- 把片段触发设为简单单行词(比如
migratetfuel),或用auto自动触发(需注意误触发) - 在片段的代码块中,手动读取光标上下的行内容,拼接成多行字符串后做正则匹配
- 提取匹配组后生成目标内容
修改后的示例片段
snippet migratetfuel "migrate all tfuel reward" b `!p # 读取当前行及下方两行内容 lines = [] for i in range(3): line = snip.buffer[snip.line + i] lines.append(line.strip('\n')) multi_line_text = '\n'.join(lines) # 多行正则匹配 import re pattern = r'(\d{4}-\d{2}-\d{2}) tfuel reward\n.*equity.*(\d+) tfuel\n.*tfuel' match = re.match(pattern, multi_line_text) if match: date = match.group(1) amount = int(match.group(2)) # 调用自定义函数生成内容 snip.rv = f"""{date} tfuel reward income:stak_reward:tfuel:{date} -{calc_value(date, 'tfuel', amount)} eur equity:opening_balance:stak_reward:{date} -{calc_value(date, 'tfuel', amount)} eur asset:tfuel:Liq:{date} {amount} tfuel @ {get_token_price_in_eur(date, 'tfuel')} eur equity:conv""" else: snip.rv = "" # 不匹配则不输出内容 ` endsnippet
关键说明
- 用
b选项确保触发词在行首,减少误触发 - 通过
snip.buffer访问缓冲区全部内容,snip.line是当前光标行的索引(从0开始) - 手动拼接多行文本后用Python的
re模块匹配,实现跨多行提取匹配组 - 直接用Python生成结果字符串,替代模板变量方式,灵活性更高
3. 逐行确认的实现
如果需要逐行确认更新,可以在Python代码中加入交互逻辑:
import vim # 弹出确认对话框 confirm = vim.eval('confirm("是否更新这条条目?", "&Yes\n&No", 1)') if confirm != '1': snip.rv = "" # 用户取消则不替换
内容的提问来源于stack exchange,提问作者franck
相关产品推荐
相关产品推荐

