我想补充​​gu,gU​​​和​​g~​​​,​​gt(w,iw,$,t,i(,等等)​​​.
两个目录:​​​doc/(文档)​​​和​​plugin/(插件)​​ 开始:

if !exists("g:totitle_default_keys") 
let g:totitle_default_keys = 1
endif
//文件底部,加上映射
if g:totitle_default_keys
nnoremap <expr> gt ToTitle()
xnoremap <expr> gt ToTitle()
//视觉模式
nnoremap <expr> gtt ToTitle() .. "_"

​nnoremap <expr> gt ToTitle ()​​​映射普通模式​​*操作符*​​​.这样,可​​操作符+动作/文本块​​​.
​​​nnoremap<expr>gtt ToTitle() .. "_"​​​映射​​普通​​​模式的逐行操作符(类似​​guu​​​和​​gUU​​​).​​..​​​是​​Vim​​​的串插值操作符.​​_​​​用作带​​操作符​​​的动作.
​​​_​​​表示向下计数​​1行​​​.​​gU_​​​或​​d_​​​与​​gUU​​​或​​dd​​​意思一样.
​​​<expr>​​​参数允许指定​​计数​​​.
​​​vimrc​​​在​​plugin/​​​前运行,如果不想用​​gt​​​(因为已有),而用​​gz​​​.
把​​​let g:totitle_default_keys = 0​​​放在你的​​vimrc​​​中,这样,可在​​vimrc​​​中​​自定义​​​映射.因为​​!exists ("g:totitle_default_keys")​​​和​​if g:totitle_default_keys​​都返回假.

let g:totitle_default_keys = 0

nnoremap <expr> gz ToTitle()
xnoremap <expr> gz ToTitle()
nnoremap <expr> gzz ToTitle() .. "_"

然后这样,就可以了.

! ToTitle(type = "")
if a:type ==# ""
set opfunc=ToTitle
return "g@"
endif

"细节"
if a:type != "block" && a:type != "line" && a:type != "char"
let l:words = a:type
let l:wordsArr = trim(l:words)->split("\s\+")
call map(l:wordsArr, "s:capitalize(v:val)")
return l:wordsArr->join(" ")
endif

"保存当前配置"
let l:sel_save = &selection
let l:reg_save = getreginfo('"')
let l:cb_save = &clipboard
let l:visual_marks_save = [getpos("'<"), getpos("'>")]

try
set clipboard= selection=inclusive
let l:commands = #{line: "'[V']y", char: "`[v`]y", block: "`[\<c-v>`]y"}

silent exe "noautocmd keepjumps normal! " .. get(l:commands, a:type, "")
let l:selected_phrase = getreg('"')
let l:WORD_PATTERN = "\<\k*\>"
let l:UPCASE_REPLACEMENT = "\=s:capitalize(submatch(0))"

let l:startLine = line("'<")
let l:startCol = virtcol(".")

"用户调用块操作"
if a:type ==# "block"
sil! keepj norm! gv"ad
"
keepj $
keepj pu_

let l:lastLine = line("$")

sil! keepj norm "ap
"

let l:curLine = line(".")

sil! keepj norm! VGg@
exe "keepj norm! 0\<c-v>G$h\"ad"
exe "keepj " . l:startLine
exe "sil! keepj norm! " . l:startCol . "\<bar>\"aP"
exe "keepj " . l:lastLine
sil! keepj norm! "_dG
exe "keepj " . l:startLine
exe "sil! keepj norm! " . l:startCol . "\<bar>"

"用户调用`符/行`操作
else
let l:titlecased = substitute(@@, l:WORD_PATTERN, l:UPCASE_REPLACEMENT, "g")
let l:titlecased = s:capitalizeFirstWord(l:titlecased)
call setreg('"', l:titlecased)
let l:subcommands = #{line: "'[V']p", char: "`[v`]p", block: "`[\<c-v>`]p"}
silent execute "noautocmd keepjumps normal! " .. get(l:subcommands, a:type, "")
exe "keepj " . l:startLine
exe "sil! keepj norm! " . l:startCol . "\<bar>"
endif
finally

"恢复设置"
call setreg('"', l:reg_save)
call setpos("'<", l:visual_marks_save[0])
call setpos("'>", l:visual_marks_save[1])
let &clipboard = l:cb_save
let &selection = l:sel_save
endtry
return

​opfunc​​​是什么?为什么它返回​​g@​​​?
​​​Vim​​​有个特殊操作符,​​g@​​​操作符函数.允许​​把​​​函数分配给​​opfunc​​​选项.如果把​​Foo()​​​函数赋值给​​opfunc​​​,运行​​g@w​​​时,会在下个单词上运行​​Foo()​​​.如果运行​​g@i(​​​,则我在​​内圆括号​​​中运行​​Foo()​​​.该​​操作符​​​函数对创建你自己的​​Vim​​​操作符​​至关重要​​.

set opfunc=ToTitle
return

工作原理:假设有如下映射:

nnoremap <expr> gt ToTitle()`

通过按​​gtw​​​,​​Vim​​​会检查​​opfunc​​​是否为空.如果为​​空​​​,则​​Vim​​​会给它分配​​ToTitle​​​.然后它返回​​g@​​​,本质上又一次调用​​ToTitle​​​,即可工作了.
最开始时,​​​opfunc​​​为空,因而​​a:type​​​为​​''​​​,这样​​第1段​​为真.再调用,赋值,并返回.

=ToTitle
return "g@"

刚按下​​gtw​​​后,​​gt​​​完成上述操作,并返回​​g@​​​.返回​​g@​​​后,变成​​g@w​​​.​​g@​​​为函数符号,因而把​​w​​​传递给​​g@​​​执行.就调用了​​ToTitle​​.

三种动作类型,​​符/行/块​​​.​​g@w​​​操作符,​​g@j​​​操作行,​​列前后​​​,则是操作块.​​串​​​作为​​类型参数​​​传递给​​函数​​​.
可用

function! Test(some_arg)
echom a:some_arg
endfunction
//再

:set opfunc=Test

来测试.
接着:

if a:type != "block" && a:type != "line" && a:type != "char"
let l:words = a:type
let l:wordsArr = trim(l:words)->split("\s\+")
//分开空格
call map(l:wordsArr, "s:capitalize(v:val)")
return l:wordsArr->join(" ")
//元素大写并合并.
endif
//虽然违反了单一职责原则

你可这样:

:echo ToTitle("once on a time")
//输出Once Upon a Time

接着,

let l:sel_save = &selection
let l:reg_save = getreginfo('"')
let l:cb_save = &clipboard
let l:visual_marks_save = [getpos(""<"), getpos("">")]

临时变量,保存当前状态.

set clipboard= selection=inclusive
//`selection`为包含,`clipboard`为空

默认为包含,见​​:h'clipboard'​​​和​​:h'selection'​​ 然后是,

:commands = #{line: "'[V']y", char: "`[v`]y", block: "`[\<c-v>`]y"}
silent exe "noautocmd keepjumps normal! " .. get(l:commands, a:type, "")
//安静,为静转执行,否则在屏幕底部显示通知.

​#{}​​​为字典.
​​​"l:commands"​​​局部变量为以​​"lines","char"​​​和​​"block"​​​为键的​​哈希​​​.
​​​noautocmd​​​,执行​​后续命令​​​而不触发​​自动命令​​​.
​​​keepjumps​​​,在​​移动​​​时不记录​​光标移动​​​.
在​​​Vim​​​中,​​某些动作​​​会在​​更改,跳转和标记​​​列表中自动记录.这可以避免.使用​​noautocmd​​​和​​keepjumps​​​目的是防止​​副作用​​​.​​normal​​​按普通命令执行​​命令串​​​,​​..​​​是​​Vim​​​的​​串插值​​​语法.​​get()​​​是接受列表,​​blob​​​或字典的​​getter​​​方法.这里,传递给它的是​​l:commands​​​字典.
关键是​​​a:type​​​.​​a:type​​​是:​​符行块​​​之一.因此,如果​​a:type​​​是​​'line'​​​,你执行​​"noautocmd keepjumps normal! '[V']y"​​​.更多信息,见​​:h silent,:h:exe,:h:noautocmd,:h:keepjumps,:h:normal​​​和​​:hget()​​​.
​​​'[​​​和​​']​​​记住​​g@​​​命令的​​开始和结束​​动作位置.

按​​'[​​​会移动光标到​​第一行​​​,这是运行​​g@​​​时开始地方.​​V​​​是​​逐行可视​​​模式命令.最后,​​']​​​移动​​光标​​​到先前​​更改或复制出​​​的文本末尾,但此时,它移动​​光标​​​到最后一次​​g@​​​操作末尾.最后​​y​​​复制出​​选定文本​​​.
上段,就是复制​​​要执行文本​​.其他命令类似,

let l:commands = #{line: "'[V']y", char: "`[v`]y", block: "`[\<c-v>`]y"}
//类似操作.

接着:

let l:selected_phrase = getreg('"')

取​​无名​​​寄存器内容.然后是​​正则​​:

let l:WORD_PATTERN = "\<\k*\>"

​\<​​​和​​\>​​​是单词边界,​​\k​​​是关键字​​模式​​.这是匹配模式.最后有

let l:UPCASE_REPLACEMENT = "\=s:capitalize(submatch(0))"

模式.用​​\=​​​.​​submatch(0)​​为整个匹配.

let l:startLine = line("'<")
//返回行号
let l:startCol = virtcol(".")
//光标列.先保存

处理块操作:

if a:type ==# "block"
sil! keepj norm! gv"ad
"
keepj $
keepj pu_

let l:lastLine = line("$")

sil! keepj norm "ap
"

let l:curLine = line(".")

sil! keepj norm! VGg@
exe "keepj norm! 0\<c-v>G$h\"ad"
exe "keepj " . l:startLine
exe "sil! keepj norm! " . l:startCol . "\<bar>\"aP"
exe "keepj " . l:lastLine
sil! keepj norm! "_dG
exe "keepj " . l:startLine
exe "sil! keepj norm! " . l:startCol . "\<bar>"

​sil!​​​静默运行,​​keepj​​​在​​移动​​​时保留​​跳转历史​​​.然后执行普通​​gv"ad​​​命令.​​gv​​​选择最后一个​​可视高亮​​​显示文本(在​​pancakes​​​例中,它将重新​​高亮​​​显示所有三个​​'cakes'​​​).​​"ad​​​删除他们,并在​​a​​​寄存器​​存储​​​.结果,现在,​​a​​​中存储了​​3​​个块.然后

keepj $ 
keepj pu _

​$​​​移动到​​文件​​​最后一行.​​pu_​​​在​​光标​​​位置下方​​插入一行​​​,​​keepj​​​不会改变​​跳转历史​​.

let l:lastLine = line("$")
//行号存储在`lastLine`变量中

再复制进尾行:

sil!

然后,​​存储​​​光标所在​​当前行​​位置:

let l:curLine = line(".")

然后是:

sil! keepj norm! VGg@
exe "keepj norm! 0\<c-v>G$h\"ad"
exe "keepj " . l:startLine
exe "sil! keepj norm! " . l:startCol . "\<bar>\"aP"
exe "keepj " . l:lastLine
sil! keepj norm! "_dG
exe "keepj " . l:startLine
exe "sil! keepj norm! " . l:startCol . "\<bar>"

这里:

sil! keepj norm!

递归调用​​VGg@​​​,​​VG​​​可视块,​​g@​​​递归调用​​函数​​.这样都大写了.

exe "keepj norm! 0\<c-v>G$h\"ad"

删除高亮,并存储在​​a​​​寄存器中.​​h​​​为右移,​​c-v​​​为可视,​​G​​为尾.

exe "keepj " . l:startLine

光标移回​​起始行​​.再粘贴:

exe "sil! keepj norm! " . l:startCol . "\<bar>\"aP"

​<bar>​​​是​​|​​动作,为跳至多少列的意思.再粘贴.

exe "keepj " . l:lastLine
sil! keepj norm! "_dG
exe "keepj " . l:startLine
exe "sil! keepj norm! " . l:startCol . "\<bar>"

最后,删除.清理,回到原位.
然后是​​​行/符​​代码.

:titlecased = substitute(@@, l:WORD_PATTERN, l:UPCASE_REPLACEMENT, "g")
//关键.
let l:titlecased = s:capitalizeFirstWord(l:titlecased)
call setreg('"', l:titlecased)
let l:subcommands = #{line: "'[V']p", char: "`[v`]p", block: "`[\<c-v>`]p"}
silent execute "noautocmd keepjumps normal! " .. get(l:subcommands, a:type, "")
exe "keepj " . l:startLine
exe "sil! keepj norm! " . l:startCol . "\<bar>"

​@@​​​包含​​无名​​​寄存器文本.​​l:WORD_PATTERN​​​是单个​​关键字​​​匹配.​​l:UPCASE_REPLACEMENT​​​调用​​capitalize()​​​命令,​​g​​替换所有.

let l:titlecased = s:capitalizeFirstWord(l:titlecased)
//保证首字母总大写.如an不会大写时.

接着

call setreg('"', l:titlecased)

​放入​​无名寄存器.

let l:subcommands = #{line: "'[V']p", char: "`[v`]p", block: "`[\<c-v>`]p"}
silent execute "noautocmd keepjumps normal! " .. get(l:subcommands, a:type, "")

这里用​​p​​粘贴.

exe "keepj " . l:startLine
exe "sil! keepj norm! " . l:startCol . "\<bar>"

移回来.恢复设置:

call setreg('"', l:reg_save)
call setpos("'<", l:visual_marks_save[0])
call setpos("'>", l:visual_marks_save[1])
let &clipboard = l:cb_save
let &selection = l:sel_save

​无名,位置,剪切板及选区​​​.
用​​​:set ft=help​​​设置​​文档​​​类型.
关键字,​​​*totitle*​​​这样写.也可用​​|​​​包围​​关键字​​​.这是​​vim​​​的内部链接.​​C-]​​​跳进,​​C-[​​​跳出.
​​​:helptags ~/.vim/doc​​​创建新的标签文件,这样就可​​搜索​​了.