输入系统
输入系统
- Rime 输入法
- latex-suite 自动补全
- Vim 编辑跳转及重构
基本原则:上限足够高,我可以不用,但你必须得有
Rime 输入法
- rime/home: Rime::Home is home to Rime users and developers (github.com)
- SivanLaai/rime-pure: 【rime 小狼毫\trime 同文】手机/PC一站式配置 (github.com)
- fkxxyz/rime-cloverpinyin: 🍀️四叶草拼音输入方案,做最好用的基于rime开源的简体拼音输入方案! (github.com)
- Rime配置指南
中文输入法是一个痛点,目前尚无完美的方案解决。我曾尝试过微软输入法,手心输入法,搜狗输入法,百度输入法,讯飞输入法,rime 输入法等,都不算完美,基于以下几个方面:
- 隐私问题:输入法能够窥探到密码等个人隐私,尤其是类似搜狗这样的,每天输入的字符都会被严格记录
- 跨平台问题:mac,win,手机使得输入体验会有断层
- 定制:如今学习工作离不开打字,定制输入法让自己更舒服是一件终身大事
rime 输入法我是下了又删,下了又删,即使我有一些技术背景,但依然搞得有点头大。原因是他没有一套开箱即用的配置,丢给你一个毛坯房,挺痛苦的。但自从解除了四叶草输入方案,渐渐体会到 rime 输入引擎的强大之处。我并不打算高度定制 rime,但我选择的软件上限一定要高,因为我不能保证以后不会去折腾,不能保证未来没有大佬提供强大且友好的支持。这里给出 windows 小狼毫 Rime 配置的基础概览,最好直接照抄四叶草方案,搞清楚每个文件的定义即可。
配置概览
rime 输入法的配置文件分为两处 (就是安装时你指定的两处位置):程序文件夹、用户文件夹右击托盘图标 可以选择 打开『程序文件夹』和『用户文件夹』
- 程序文件夹:Rime 的安装目录
- 用户文件夹:自定义的一些配置,如果需要改用户文件夹目录,需要在 Rime 的安装目录中运行
WeaselSetup.exe,重新选择。
注意:在程序文件夹中的所有文件都不要动,虽然改动这里的文件也能达到自定义的效果,但每次更新都会被重置为默认设置。
我们采用“打补丁”的方式对程序文件夹中的文件进行修改,即在用户文件夹中创建 同名的 custom 文件。比如要对 default.yaml 文件修改,就创建 default.custom.yaml。
一般来说,我们需要创建的文件就三个
- default.custom.yaml 全局输入方案补丁,即定制内容对所有的输入方案都有效
- weasel.custom.yaml 全局皮肤样式补丁,分为样式和颜色两部分,对所有输入方案有效
- luna_pinyin.custom.yaml 朙月拼音 输入方案补丁,定制内容只对该方案有效,如果要修改其他方案,创建“方案名.custom.yaml”文件即可
- 方案名.schema.yaml 输入方案,直接覆盖程序文件夹下的该方案
- symbols.yaml 符号方案,对引入该文件的输入方案有效,用来定义按键输出的字符。因为是引用生效,所以自己的符号方案可以随意命名
- installation.yaml 定义用户资料同步的位置
- 词典名.dict.yaml 固定词典文件,对引入该文件的输入方案有效,用来定义能打出的词汇
- Custom_phrase.txt 用户词典文件,对所有输入方案有效,用来添加用户想添加的词汇
- rime.lua 函数文件,对引入该函数的输入方案有效
//default.custom.yaml
patch:
# 可选的输入方案列表
schema_list:
- {schema: luna_pinyin}
# 候选栏显示的词语个数
"menu/page_size": 6
# 切换/热键 ctrl+`(tab上面那个键)
# 这里的切换指 输入方案及其设置的选择
"switcher/hotkeys":
-"Control+grave"
# 处理英文模式及中英文切换
# 大小写锁定键亮时 输出大写-true 输出小写-false
ascii_composer/good_old_caps_lock: true
ascii_composer/switch_key:
# clear 清除已输入的字符
# noop 什么也不做
# commit_code 已输入的字符上屏
# commit_text 已输入的字符对应的词语上屏
# inline_ascii 输入法转变并可以继续输入
# 大小写锁定键
# 这个键位最好不要设置为noop(可以尝试)
Caps_Lock: clear
# 左shift
Shift_L: inline_ascii
Shift_R: inline_ascii
Control_L: noop
Control_R: noop
//weasel.custom.yaml
patch:
"style/color_scheme": Time_water # 应用的配色方案
"style/font_face": "Microsoft YaHei" # 应用的字体
"style/font_point": 13 # 字号大小
"style/horizontal": true # 候选栏横排显示
"style/inline_preedit": false # 隐藏打字栏
"style/display_tray_icon": false # 不显示托盘图标
"style/layout/spacing": 8 # 打字栏与候选栏的间距
"style/layout/border_width": 2 # 边框宽度
"style/layout/margin_x": 10 # 候选字左右边距
"style/layout/margin_y": 10 # 候选字上下边距
"style/layout/candidate_spacing": 10 # 候选字间隔
"style/layout/hilite_spacing": 3 # 序号和候选字之间的间隔
"style/layout/round_corner": 10 # 候选字背景色块圆角幅度
"style/layout/hilite_padding": 4 # 候选字背景色色块高度和打字栏未选择字背景色块高度 若想候选字背景色块无边界填充候选框,仅需其高度和候选字上下边距一致即可
# 配色方案名称(用于使用 建议英文)
"preset_color_schemes/Time_water":
name: windily # 名称 只用于阅读
author: cloud # 作者 只用于阅读
text_color: 0x969483 # 打字栏除正在选择字外的字 字体颜色
back_color: 0xf2f2f2 # 打字栏与候选栏 背景色
border_color: 0xffccff # 边框颜色
#label_color: 0xffffff # 候选栏 序号颜色
candidate_text_color: 0x000000 # 候选栏 未候选字颜色
comment_text_color: 0xd28b26 # 候选栏 补充说明 字体颜色
hilited_text_color: 0x394bdd # 打字栏 正在选择的字 字体颜色
hilited_back_color: 0xf2f2f2 # 打字栏 正在选择的字 背景色
hilited_candidate_text_color: 0xff2288 # 候选栏 候选字颜色
hilited_candidate_back_color: 0xffccff # 候选栏 候选字背景色

latex-suite 自动补全
自动补全一般分为三类:
- 根据笔记库自动补全
- 根据上下文自动补全
- 根据配置文件自动补全
这三类的定制性依次升高,实际上是精准补全 vs 全面补全的权衡。如果补全的来源是整个笔记库,提示的条数会在 9-10 条左右,随着笔记的增加,精准率会逐渐上升,同时给笔记软件带来的负荷也会上升。基本原理普遍是注册 codemirror 的扩展,稳定性会受到 ob 的影响。之前 ob 对 codemirror 做了一层封装,想要抹平 codemirror 升级带来对插件的影响,但由于很多是不可控的特性(比如把我最爱的光标美化给搞没了),此方案遂被放弃。
obsidian 里有极其多的自动补全方案,包括但不限于:
- latex-suite:目前使用的插件,开发者友好,且强烈依赖它。最重要的是给了一系列 latex 的输入规则,这是其他插件不可媲美的,功能也足够强大。
- typing-transformer-obsidian:后起之秀,自定义程度也高,是中文开发者,着重解决中文输入痛点,比如¥符号等。核心特性是支持局部格式化。
- various-complements:全文补全,整库补全的代表,支持自定义词典,足够强大。但我贼烦它时不时跳出来,让我失去了控制感。
- text-snippets:早年试用过,配置文件补全的代表,但貌似开发者并不强烈依赖它,停更了一段时间,于是我选择了其他插件
- text-expander:最大的特性是支持脚本补全,是比正则补全更加强大自由的补全方式,试用了一段时间,对脚本的需求不是特别大。我甚至在 latex-suite 下开了 issue,开发者鸽了我,目前来看也无所谓了。
- completr:扫描全库补全,对我来说 latex 补全远远不及 latex-suite
- autocomplete:开发者放弃了这个插件
latex-suite 简单易学,前置条件是正则表达式。其目前分为三种模式 (看 issue 还在增加),options 是模式,t 代表文本模式,m 代表 latex 模式,r 代表正则模式,A 代表自动触发。$0 代表光标跳转的位置,[[0]] 代表正则表达式替换的位置。因此可实现:
- 正常编辑字符串/正则自动触发补全
- 正常编辑
tab触发字符串/正则补全 - latex 模式自动补全,并带有预览框
vim 编辑跳转重构
ob 的编辑跳转比较羸弱,当碰到大文本量时(万字以上),很难优雅的跳转。于是可以采用 vim 模式进行增强,主要场景包括:
- 选择:当选择 100 行时,vim 可以用
100G方便的跳转和选择,鼠标选择由于所见即所得,会跳得头疼 - 重构:把所有二级标题改为一级标题,
%s/^##/#即可处理完成 - 标记:vim 的标记可以方便的给当前位置做标记,想要的时候跳转
- 自动居中:
zz使得能让当前光标行居中,虽然有插件可以自动居中,但显然有时候我不想让他一直居中 - 增强快捷键:vim 的快捷键可以分配给 ob 的命令,不然键盘快捷键属实不够
- ....
ob 里 vim 增强插件有:
- esm7/obsidian-vimrc-support: A plugin for the Obsidian.md note-taking software (github.com):最好用的是将无名寄存器设置为剪贴板
- ALONELUR/vim-im-select-obsidian: Obsidian plugin: vim im select (github.com):妈妈再也不担心我输入中文了(虽然还是会误触,但好点了)
- tgrosinger/leader-hotkeys-obsidian: Use a leader-key (tmux style) for hotkeys in Obsidian.md (github.com):leader 键,老板键
注意:ob 的 vim 模式依然有点小问题,比如打中文打着打着不能实时渲染了,得刷新才行。无报错,无固定复现路径。
ob 支持的 vim 特性
目前 ob 支持的 vim 特性如下,不算太多,但足够使用:
var defaultKeymap = [
// Key to key mapping. This goes first to make it possible to override
// existing mappings.
{ keys: '<Left>', type: 'keyToKey', toKeys: 'h' },
{ keys: '<Right>', type: 'keyToKey', toKeys: 'l' },
{ keys: '<Up>', type: 'keyToKey', toKeys: 'k' },
{ keys: '<Down>', type: 'keyToKey', toKeys: 'j' },
{ keys: 'g<Up>', type: 'keyToKey', toKeys: 'gk' },
{ keys: 'g<Down>', type: 'keyToKey', toKeys: 'gj' },
{ keys: '<Space>', type: 'keyToKey', toKeys: 'l' },
{ keys: '<BS>', type: 'keyToKey', toKeys: 'h', context: 'normal'},
{ keys: '<Del>', type: 'keyToKey', toKeys: 'x', context: 'normal'},
{ keys: '<C-Space>', type: 'keyToKey', toKeys: 'W' },
{ keys: '<C-BS>', type: 'keyToKey', toKeys: 'B', context: 'normal' },
{ keys: '<S-Space>', type: 'keyToKey', toKeys: 'w' },
{ keys: '<S-BS>', type: 'keyToKey', toKeys: 'b', context: 'normal' },
{ keys: '<C-n>', type: 'keyToKey', toKeys: 'j' },
{ keys: '<C-p>', type: 'keyToKey', toKeys: 'k' },
{ keys: '<C-[>', type: 'keyToKey', toKeys: '<Esc>' },
{ keys: '<C-c>', type: 'keyToKey', toKeys: '<Esc>' },
{ keys: '<C-[>', type: 'keyToKey', toKeys: '<Esc>', context: 'insert' },
{ keys: '<C-c>', type: 'keyToKey', toKeys: '<Esc>', context: 'insert' },
{ keys: '<C-Esc>', type: 'keyToKey', toKeys: '<Esc>' }, // ipad keyboard sends C-Esc instead of C-[
{ keys: '<C-Esc>', type: 'keyToKey', toKeys: '<Esc>', context: 'insert' },
{ keys: 's', type: 'keyToKey', toKeys: 'cl', context: 'normal' },
{ keys: 's', type: 'keyToKey', toKeys: 'c', context: 'visual'},
{ keys: 'S', type: 'keyToKey', toKeys: 'cc', context: 'normal' },
{ keys: 'S', type: 'keyToKey', toKeys: 'VdO', context: 'visual' },
{ keys: '<Home>', type: 'keyToKey', toKeys: '0' },
{ keys: '<End>', type: 'keyToKey', toKeys: '$' },
{ keys: '<PageUp>', type: 'keyToKey', toKeys: '<C-b>' },
{ keys: '<PageDown>', type: 'keyToKey', toKeys: '<C-f>' },
{ keys: '<CR>', type: 'keyToKey', toKeys: 'j^', context: 'normal' },
{ keys: '<Ins>', type: 'keyToKey', toKeys: 'i', context: 'normal'},
{ keys: '<Ins>', type: 'action', action: 'toggleOverwrite', context: 'insert' },
// Motions
{ keys: 'H', type: 'motion', motion: 'moveToTopLine', motionArgs: { linewise: true, toJumplist: true }},
{ keys: 'M', type: 'motion', motion: 'moveToMiddleLine', motionArgs: { linewise: true, toJumplist: true }},
{ keys: 'L', type: 'motion', motion: 'moveToBottomLine', motionArgs: { linewise: true, toJumplist: true }},
{ keys: 'h', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }},
{ keys: 'l', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: true }},
{ keys: 'j', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, linewise: true }},
{ keys: 'k', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, linewise: true }},
{ keys: 'gj', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: true }},
{ keys: 'gk', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: false }},
{ keys: 'w', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false }},
{ keys: 'W', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false, bigWord: true }},
{ keys: 'e', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, inclusive: true }},
{ keys: 'E', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true }},
{ keys: 'b', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }},
{ keys: 'B', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false, bigWord: true }},
{ keys: 'ge', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, inclusive: true }},
{ keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }},
{ keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }},
{ keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }},
{ keys: '(', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: false }},
{ keys: ')', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: true }},
{ keys: '<C-f>', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }},
{ keys: '<C-b>', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }},
{ keys: '<C-d>', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }},
{ keys: '<C-u>', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }},
{ keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
{ keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
{keys: "g$", type: "motion", motion: "moveToEndOfDisplayLine"},
{keys: "g^", type: "motion", motion: "moveToStartOfDisplayLine"},
{keys: "g0", type: "motion", motion: "moveToStartOfDisplayLine"},
{ keys: '0', type: 'motion', motion: 'moveToStartOfLine' },
{ keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' },
{ keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }},
{ keys: '-', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, toFirstChar:true }},
{ keys: '_', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},
{ keys: '$', type: 'motion', motion: 'moveToEol', motionArgs: { inclusive: true }},
{ keys: '%', type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true, toJumplist: true }},
{ keys: 'f<character>', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }},
{ keys: 'F<character>', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: false }},
{ keys: 't<character>', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: true, inclusive: true }},
{ keys: 'T<character>', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }},
{ keys: ';', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: true }},
{ keys: ',', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: false }},
{ keys: '\'<character>', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true, linewise: true}},
{ keys: '`<character>', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true}},
{ keys: ']`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
{ keys: '[`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
{ keys: ']\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
{ keys: '[\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
// the next two aren't motions but must come before more general motion declarations
{ keys: ']p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true}},
{ keys: '[p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true}},
{ keys: ']<character>', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: true, toJumplist: true}},
{ keys: '[<character>', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: false, toJumplist: true}},
{ keys: '|', type: 'motion', motion: 'moveToColumn'},
{ keys: 'o', type: 'motion', motion: 'moveToOtherHighlightedEnd', context:'visual'},
{ keys: 'O', type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'},
// Operators
{ keys: 'd', type: 'operator', operator: 'delete' },
{ keys: 'y', type: 'operator', operator: 'yank' },
{ keys: 'c', type: 'operator', operator: 'change' },
{ keys: '=', type: 'operator', operator: 'indentAuto' },
{ keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }},
{ keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }},
{ keys: 'g~', type: 'operator', operator: 'changeCase' },
{ keys: 'gu', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, isEdit: true },
{ keys: 'gU', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, isEdit: true },
{ keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }},
{ keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }},
{ keys: 'gn', type: 'motion', motion: 'findAndSelectNextInclusive', motionArgs: { forward: true }},
{ keys: 'gN', type: 'motion', motion: 'findAndSelectNextInclusive', motionArgs: { forward: false }},
// Operator-Motion dual commands
{ keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }},
{ keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }},
{ keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
{ keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'},
{ keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'expandToLine', motionArgs: { linewise: true }, context: 'normal'},
{ keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'},
{ keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'},
{ keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'},
{ keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'},
{ keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'},
{ keys: '<C-u>', type: 'operatorMotion', operator: 'delete', motion: 'moveToStartOfLine', context: 'insert' },
{ keys: '<C-w>', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' },
//ignore C-w in normal mode
{ keys: '<C-w>', type: 'idle', context: 'normal' },
// Actions
{ keys: '<C-i>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }},
{ keys: '<C-o>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }},
{ keys: '<C-e>', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }},
{ keys: '<C-y>', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }},
{ keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' },
{ keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' },
{ keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' },
{ keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' },
{ keys: 'gi', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'lastEdit' }, context: 'normal' },
{ keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' },
{ keys: 'gI', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'bol'}, context: 'normal' },
{ keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' },
{ keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' },
{ keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' },
{ keys: 'v', type: 'action', action: 'toggleVisualMode' },
{ keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }},
{ keys: '<C-v>', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},
{ keys: '<C-q>', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},
{ keys: 'gv', type: 'action', action: 'reselectLastSelection' },
{ keys: 'J', type: 'action', action: 'joinLines', isEdit: true },
{ keys: 'gJ', type: 'action', action: 'joinLines', actionArgs: { keepSpaces: true }, isEdit: true },
{ keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }},
{ keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }},
{ keys: 'r<character>', type: 'action', action: 'replace', isEdit: true },
{ keys: '@<character>', type: 'action', action: 'replayMacro' },
{ keys: 'q<character>', type: 'action', action: 'enterMacroRecordMode' },
// Handle Replace-mode as a special case of insert mode.
{ keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }, context: 'normal'},
{ keys: 'R', type: 'operator', operator: 'change', operatorArgs: { linewise: true, fullLine: true }, context: 'visual', exitVisualBlock: true},
{ keys: 'u', type: 'action', action: 'undo', context: 'normal' },
{ keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true },
{ keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true },
{ keys: '<C-r>', type: 'action', action: 'redo' },
{ keys: 'm<character>', type: 'action', action: 'setMark' },
{ keys: '"<character>', type: 'action', action: 'setRegister' },
{ keys: 'zz', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }},
{ keys: 'z.', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
{ keys: 'zt', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }},
{ keys: 'z<CR>', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
{ keys: 'zb', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }},
{ keys: 'z-', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
{ keys: '.', type: 'action', action: 'repeatLastEdit' },
{ keys: '<C-a>', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: true, backtrack: false}},
{ keys: '<C-x>', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: false, backtrack: false}},
{ keys: '<C-t>', type: 'action', action: 'indent', actionArgs: { indentRight: true }, context: 'insert' },
{ keys: '<C-d>', type: 'action', action: 'indent', actionArgs: { indentRight: false }, context: 'insert' },
// Text object motions
{ keys: 'a<character>', type: 'motion', motion: 'textObjectManipulation' },
{ keys: 'i<character>', type: 'motion', motion: 'textObjectManipulation', motionArgs: { textObjectInner: true }},
// Search
{ keys: '/', type: 'search', searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},
{ keys: '?', type: 'search', searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
{ keys: '*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
{ keys: '#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
{ keys: 'g*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
{ keys: 'g#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
// Ex command
{ keys: ':', type: 'ex' }
];
定位

| 操作 | 快捷键 | 备注 |
|---|---|---|
| 下一个单词开头 | w | 非标点也算单词,vimrc 中可调 |
| 以空格为分隔向后移动 | W | |
| 上一个单词的开头 | b | 非标点也算单词,vimrc 中可调 |
| 以空格为分隔向前移动 | B | |
| 下一个单词的结尾 | e | 非标点也算单词,vimrc 中可调 |
| 上一个单词的结尾 | ge | 非标点也算单词,vimrc 中可调 |
| 移动到行首第一个字符 | 0 | |
| 移动到行首非空字符 | ^ | 我改到了 H |
| 移动到行尾 | $ | 2$ 代表下一行的行尾,我改到了 L |
| 行内向后移动到一个指定字符 | f{char} | 计数前缀 ; 重复,, 反向重复 |
| 行内向前移动到一个指定字符 | F{char} | 计数前缀 ; 重复,, 反向重复 |
| 行内向后移动到目标字符的前一个字符 | t{char} | 计数前缀 ; 重复,, 反向重复 |
| 行内向前移动到目标后一个字符 | T{char} | 计数前缀 ; 重复,, 反向重复 |
| 单词匹配 | * | |
| 括号匹配 | % | |
| 移动到指定的行 | {num}G 或者 :{num} | |
| 移动到第一行 | gg | |
| 移动到最后一行 | G | |
| 百分号定位 | {num}% | |
| 可视区域定位 | H M L | 定位到可视区域的高 hign,中 middle,低 low |
| 向下滚半屏 | ctrl + d | down |
| 向上滚半屏 | ctrl + u | up |
| 向下滚整屏 | ctrl + f | forward |
| 向上滚整屏 | ctrl + b | backward |
| 让光标所处行在顶部 | zt | top |
| 让光标所处行在中间 | zz | |
| 让光标所处行在底部 | zb | |
| 跳转到上一个位置 | `` | |
| 上次修改的地方 | `. | |
| 上次插入的地方 | `^ | |
| 上次复制和修改的起始位置 | `[ | |
| 标记位置 | m | 标记一个 char 位置 |
| 跳转标记位置 | ` | |
| 在行内跳转到下一指定字符前 | t | |
| 在行内跳转到上一指定字符上 | f | |
| 在行内跳转到上一指定字符后 | t |
编辑
| 操作 | 快捷键 | 备注 |
|---|---|---|
| 增加从当前行到文档末尾处的缩进层级 | >G | |
| 行首插入 | I | |
| 行尾插入 | A | |
| 修改当前光标至行尾的内容 | C | |
| 修改当前字符 | r | |
| 进入替换模式 | R | |
| 修改当前字符 | s | |
| 修改当前整行内容 | S | |
| 在下一行插入 | o | |
| 在上一行插入 | O | |
| 连续修改相同单词 | * -> cw{new char} -> n -> . | obsidian 中中英文有问题 |
| 删除一个单词 | daw | |
| 数字递增 | ctrl + a | 不必在数字上, 180 + ctrl + a,对数字加 180 |
| 数字递减 | ctrl + x | 不必在数字上 |
| 删除一个段落 | dap | |
| 自动缩进 | = | |
| 增加缩进 | > | |
| 减少缩进 | < |
改变大小写
| 操作 | 快捷键 | 备注 |
|---|---|---|
| 大写 | gU | |
| 小写 | gu | |
| 反转大小写 | g~ |
查找
| 操作 | 快捷键 | 备注 |
|---|---|---|
| 页内向后查找 | /{string} | 特殊字符用 \ 转义, n 下一个 , N 上一个 |
| 页内向前查找 | ?{string} | 特殊字符用 \ 转义, n 下一个 , N 上一个 |
| 查找已有单词 | * | 在单词下面按 * |
| 匹配单词末尾 | /{word}\ | |
| 匹配单词开始 | /\<{word} | |
| 全字匹配 | /\<{word}\ | |
| 匹配行首 | /^{word} | 正则匹配 |
| 匹配行尾 | /{word}$ | 正则匹配 |
| 正则匹配 |
替换
| 操作 | 快捷键 | 备注 |
|---|---|---|
| 行内替换 | s/from/to/flag | &重复,u 回退 |
| 全局替换 | %s/from/to/flag | |
| 指定行替换 | {num},{num}s/from/to/flag | |
| 标记行内替换 | ||
| 指定范围替换 | .+{num},$-{num}s/from/to/flag |
复制粘贴
| 操作 | 快捷键 | 备注 |
|---|---|---|
| 复制到系统剪贴板 | "+y,shift + insert | |
折叠
| 操作 | 快捷键 | 备注 |
|---|---|---|
| 折叠 | zc | |
| 展开 | zo |
可视模式与选区
v 可以进入可视模式
| 操作 | 快捷键 | 备注 |
|---|---|---|
| 激活面向字符的可视模式 | v | |
| 激活面向行的可视模式 | V | |
| 激活面向列块的可视模式 | ctrl + v | |
| 重选上次选区 | gv | |
| 切换选区活动端 | o | |
| 可视选择一个单词 | viw | |
| 选择标签内内容 | vit | obsidian 内不适用 |
| 选区高亮的第一行(用于命令模式) | `< | |
| 选区高亮的最后行(用于命令模式) | `> | |
| 插入 | I A | |
| 修改整个可视列块 | c | |
| 修改整个可视列块即块后内容 | C | |
| 连接行 | J |
Vim 的文本对象由两个字符组成,第一个字符永远是 i 或是 a。一 般以 i 开头的文本对象会选择分隔符内部的文本,而以 a 开头的文本对 象会选择包括分隔符在内的整个文本。为了便于记忆,可以把 i 成“inside”,而把 a 想成“around”或“all”。


记录与回放/宏录制
- "q{register}" 命令启动一次击键记录,结果保存到 {register} 指定的寄存器中。寄存器名可以用 a 到 z 中任一个字母表示。
- 输入你的命令。
- 键入 q (后面不用跟任何字符) 命令结束记录。
现在,你可以用 "@{register}" 命令执行这个宏。
快捷键管理
- cogscides/obsidian-keyboard-analyzer: Obsidian plugin to display command hotkeys on a visible keyboard layout (github.com):Ob 里的快捷键可视化分配
- OpenArk - Opensource Anti Rootkit (blackint3.com):开源的 windows 快捷键检测工具,我主用 windows,所以其他的不砸在乎
心得体会:别 tm 想着快捷键管理,常用的快捷键自然而然会记住,冲突了自然而然会替换,分配啥的随当时心意就好。
文本格式化
格式化的目的不仅仅是强迫症为了好看,更多的是为了文本一致性。一致的文本有利于检索。这里的一致性有很多可折腾的,但现在我并不想展开,它和整个笔记系统相关(这不是我故作高深,详情可以参考前端 linter,prettier 的爱恨情仇,每一个语句后面需不需要引号都能写篇长文论证。数以百计的 linter 规则,还有 git commit 自动化, html,css 等规则,足以说明健全的笔记系统,格式化的重要性足够重要)。只给出一致性带给我们的优势:==给整个笔记库应用统一规则,给不同笔记应用不同的规则。==这里的规则一方面限制我们书写笔记的自由度,使得文本检索,文本排版,文本索引,文本属性得以更好发挥,另一方面,给我们更大的自由度,即自动化。
- 限制自由度:每篇笔记都按照特定格式,特定文本,甚至特定文件夹组织,笔记完全是一个规则化的系统。
- 更大自由度:正是因为限制了自由度,才能用脚本或者插件,在规则的基础上找到修改,重构,检索的方法。md 一般以
#为标题 (其实---也行),这样就能自动检索,修改标题,这是非格式化的文本做不到的。
目前 ob 的格式化插件也很多:
- platers/obsidian-linter: An Obsidian plugin that formats and styles your notes with a focus on configurability and extensibility. (github.com):强大,高度定制化,下限高,上限不低,能自定义运行命令。这个自定义运行命令是我加的,但现在看来多此一举,因为我可以用 quickadd 先运行 linter,再运行自定义命令,完全不用写一行代码。我人傻了。
- hipstersmoothie/obsidian-plugin-prettier: Format obsidian.md notes using prettier (github.com):功能羸弱
- cristianvasquez/obsidian-prettify: A markdown prettifier for obsidian (github.com):不再维护
其实文本格式化还有一个用途,就是 lint。比如笔记少些了 tag,哪个位置差点什么,能在 lint 时提醒自己。当然,这属于高度定制化功能,得 用 linter 插件写脚本。
版本管理
git 的重要性不言而喻,但非程序员很少有人能很好的利用起来这个强大的工具,具体这是个啥玩意儿,我又不是写教程,自己去学去。这里给出我的心得,git 教程应该不会有:
一般的 git 工作流:
- 创建新的分支 branch
- 创建提交 commit 保存你的笔记,使得该阶段可回溯
- 进行工作,完成工作,并提交 commit
- 合并分支到主分支
这种工作流往往会产生一些不必要的 commit 节点,使得 git 的历史是不整洁,不利于检查的。偶尔还需要 rebase,squash merge 等操作。
这里推荐一个新的工作流:
- 创建新的分支 branch
- 提交 commit 保存笔记,不必在意注释内容
- 重置 reset 会初始状态,重新 add 并 commit,写注释
- 合并分支到主分支
$ git reset origin/main
Unstaged changes after reset:
M src/components/Footer/Footer.tsx
M src/components/Nav/Nav.css
M src/components/Nav/Nav.tsx
M src/components/Posts/Post.tsx
M src/components/Posts/PostList.tsx
$ git status
On branch feature-branch
Your branch is behind 'origin/feature-branch' by 3 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: src/components/Footer/Footer.tsx
modified: src/components/Nav/Nav.css
modified: src/components/Nav/Nav.tsx
modified: src/components/Posts/Post.tsx
modified: src/components/Posts/PostList.tsx
$ git add src/components/Nav/Nav.css
$ git add src/components/Nav/Nav.tsx
$ git commit -m"Added new styles to navigation"
$ git add src/components/Posts/Post.tsx
$ git add src/components/Posts/PostList.tsx
$ git commit -m"Updated author images on posts"
$ git add src/components/Footer/Footer.tsx
$ git commit -m"Fixed responsive bug in footer"
当然也可借助一些前端命令行工具应用一定的规则进行提交,比如 enhance,study 等关键词,方便以后通过 git 回溯学习记录。
