You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 10:23:12