Vim 脚本实战:打造一个支持中文的精准字数统计器
在 VIM 中进行中文写作时,默认的 wc 或 g CTRL-G 统计的是字节数,中文字符会按 3 个字节计算,不符合中文写作“一个字算一个”的习惯。此外,默认状态栏只显示行号、列号,无法实时看到当前字数。
本文提供一套完整的 VIM 脚本,实现:
- 中文、英文、标点符号统一计为 1 个字符(不含换行)
- 普通模式显示 从文件开头到光标前 的字数
- Visual 模式下实时统计 选中区域 的字数(支持三种选择模式)
- 状态栏显示
已写字数/总字数,类似1200/5000
完整脚本
将以下代码添加到 ~/.vimrc 或 ~/.vim/plugin/wordcount.vim 中:
1 | " ============================================== |
核心基础知识解析
1. VimScript 变量定义与作用域
脚本中大量使用 l: 前缀,如 l:total、l:line。这是 VimScript 的局部变量作用域标识:
| 前缀 | 含义 |
|---|---|
l: |
函数内部局部变量(最常用) |
g: |
全局变量 |
s: |
脚本文件局部变量 |
a: |
函数参数 |
示例:
1 | function! Example() |
2. strchars() vs strlen() vs len()
这是实现中英文统一计数的关键函数:
| 函数 | 返回值 | 示例 (你好ab) |
|---|---|---|
strlen() |
字节数 | 8 (UTF-8中文占3字节) |
len() |
列表长度/字符串字节数 | 同上 |
strchars() |
字符数 | 4 (你/好/a/b各1) |
1 | echo strchars("你好vim") " 输出 5 |
3. 字符串比较的 # 后缀
1 | if l:m ==# 'v' |
==# 表示大小写敏感比较,==? 表示大小写不敏感。不写后缀时会受 ignorecase 选项影响,容易产生难以追踪的 bug。**最佳实践:始终使用 # 或 ?**。
4. getpos() 返回值结构
1 | let pos = getpos(".") |
pos[1]:行号pos[2]:列号(从1开始)
脚本中通过 getpos("v") 获取 Visual 模式起始位置,getpos(".") 获取光标当前位置。
5. Visual 模式的三种标识
Vim 中 mode() 函数返回值:
| 模式 | 返回值 | 对应函数 |
|---|---|---|
字符选择 v |
'v' |
GetVisualCharCountNormal() |
行选择 V |
'V' |
GetVisualCharCountLine() |
列选择 Ctrl-V |
"\x16" (十六进制 22) |
GetVisualCharCountBlock() |
注意列模式不能直接写 "\x16" 比较,需要转义。
6. 字符串切片 strpart()
1 | strpart("Hello World", 0, 5) " 返回 "Hello" |
参数说明:
- 第二个参数:起始位置(字节索引,0开始)
- 第三个参数:截取长度(字节数,非字符数!)
坑点:strpart() 按字节截取,如果截断在多字节字符中间会乱码。但本脚本与 strchars() 配合使用,且 strchars() 只计算完整字符,所以安全。
7. 列表操作:range() 与解构赋值
1 | " 生成行号范围 |
脚本中用此技巧统一选中区域的起止位置。
8. min() / max() 简化边界处理
1 | let l:from_line = min([l:s_lnum, l:e_lnum]) |
比手动 if 判断更简洁。
RulerFormat 与普通状态栏的区别
普通状态栏 (statusline)
statusline 是 Vim 底部倒数第二行,每个窗口独立显示,通常包含文件名、文件类型、Git 分支等丰富信息。典型配置:
1 | set statusline=%f\ %m\ %r\ %y\ %{&fileencoding}\ %l/%L\ %c |
特点:
- 默认不显示,需要
set laststatus=2启用 - 可以包含动态表达式
%{func()} - 支持高亮分组(
%#HighlightGroup#) - 内容较多,占据一整行
Ruler (rulerformat)
ruler 是 Vim 底部最右下角的一小段信息,默认显示光标位置和百分比。特点:
- 默认开启(
set ruler) - 占用空间极小,不干扰编辑区域
- 格式通过
rulerformat定制 - 不能使用高亮分组,功能精简
对比总结
| 特性 | statusline | ruler |
|---|---|---|
| 位置 | 底部倒数第二行 | 右下角 |
| 默认状态 | 需手动开启 | 默认开启 |
| 信息容量 | 大(可占整行) | 小(仅右下角) |
| 高亮支持 | 支持 | 不支持 |
| 表达式支持 | %{func()} |
%{func()} |
| 适用场景 | 文件信息、模式、Git | 光标位置、简单计数 |
本脚本为何选择 rulerformat
字数统计适合放在右下角,因为:
- 它只是一个辅助计数,不需要占用一整行
- 与默认的光标位置信息(
%l,%c)天然并列 - 开启
ruler的用户不会感到突兀
如果希望更丰富的展示(如目标字数进度条),可以改用 statusline,但需要额外配置 laststatus=2。
扩展建议
添加目标字数提醒
在 GetSmartCharCount() 中加入:
1 | let l:target = 5000 " 目标字数 |
保存时自动检查
1 | autocmd BufWritePre *.md :echo "当前字数: " . GetSmartCharCount() |
使用 statusline 版本(替代方案)
1 | set laststatus=2 |
常见问题
Q: 统计结果比 wc -m 少?
A: wc -m 会统计换行符,本脚本不统计换行。
Q: 列模式统计出错 E46?
A: 已修复。原因是 Vim 列模式返回值需要特殊处理十六进制 \x16。
Q: 中英文混合的 emoji 如何计数?
A: strchars() 将 emoji (如 👍) 计为 1 个字符,符合直觉。
总结
本脚本利用 VimScript 的 strchars() 实现了统一字符计数,通过 mode() 区分三种 Visual 模式,最终通过 rulerformat 优雅地展示在界面右下角。掌握这些基础知识后,你可以轻松改造状态栏、实现字数目标、多文件统计等进阶功能。
- 本文作者:scwang90
- 本文链接:https://blog.scwang90.cn/2026/04/08/linux-vim-word-count/index.html
- 版权声明:本分享所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!