Vim中使用cabbrev配置带!的命令时遇到的冲突问题
我之前一直用这段cabbrev配置(SafeQuitAll()的具体功能不重要):
cabbrev qa <C-r>=(getcmdtype()==#':' && getcmdpos()==1 ? 'call <sid>SafeQuitAll()' : 'qa')<cr>
这段配置运行正常,但现在我想添加对qa!的支持,于是给SafeQuitAll()加了个参数用来传递!的状态,还加了额外的cabbrev配置:
cabbrev qa <C-r>=(getcmdtype()==#':' && getcmdpos()==1 ? 'call <sid>SafeQuitAll(0)' : 'qa')<cr> cabbrev qa! <C-r>=(getcmdtype()==#':' && getcmdpos()==1 ? 'call <sid>SafeQuitAll(1)' : 'qa!')<cr>
但新配置没法正常工作。:qa还能正常运行,但输入qa!时,按下!后命令行会变成:
:call <SNR>2_SafeQuitAll(0)!
也就是qa!的cabbrev被qa的覆盖了,输入!会触发第一个缩写。我试过各种语法组合都没用,比如下面这段(我确定getcmdline()总是返回空字符串,这点我搞不懂):
cabbrev qa <C-r>=(getcmdtype()==#':' && getcmdpos()==1 && getcmdline()!~?'qa!' ? 'call <sid>SafeQuitAll(0)' : 'qa')<cr>
我猜问题核心是!被当成命令终止符了(是不是这样?),导致cabbrev提前触发。有没有人知道怎么解决?
问题根源是Vim的缩写匹配逻辑:当输入qa后按下!,Vim会优先匹配已有的qa缩写并立即展开,不会等待输入完整的qa!。以下是两种可行的解决办法:
方法一:合并逻辑,动态判断后续字符
放弃单独的qa!缩写,在qa的缩写逻辑里直接判断接下来输入的是否是!,然后生成对应的函数调用:
cabbrev qa <C-r>=((getcmdtype() ==# ':' && getcmdpos() == 1) ? (getchar() == getcharcode('!') ? 'call <sid>SafeQuitAll(1)' : 'call <sid>SafeQuitAll(0)') : 'qa')<cr>
这个方法的特点是输入qa后按!会直接替换成带参数1的调用,!会被吃掉;如果按其他键则展开成带参数0的调用。
方法二:限制qa缩写的触发条件
修改qa的缩写,让它只有在qa后面不是!时才触发,这样qa!的缩写就能正常匹配:
cabbrev qa <C-r>=(getcmdtype()==#':' && getcmdpos()==1 && getchar(getcmdpos()) != getcharcode('!') ? 'call <sid>SafeQuitAll(0)' : 'qa')<cr> cabbrev qa! <C-r>=(getcmdtype()==#':' && getcmdpos()==1 ? 'call <sid>SafeQuitAll(1)' : 'qa!')<cr>
这里用getchar(getcmdpos())获取光标下一个位置的字符,判断是否为!,如果是则跳过qa的缩写,让qa!的缩写生效。
关于getcmdline()返回空的说明
你之前用getcmdline()得到空字符串是正常的——在cabbrev的表达式执行时,Vim还没把你输入的qa写入命令行(缩写触发是在输入字符后、写入命令行前),所以此时getcmdline()只能获取到:,自然匹配不到qa!。
内容的提问来源于stack exchange,提问作者Rich

