输入系统
- 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} | 标记一个 char 位置 |
跳转标记位置 | `{char} | |
在行内跳转到下一指定字符前 | t{char} | |
在行内跳转到上一指定字符上 | f{char} | |
在行内跳转到上一指定字符后 | t{char} |
编辑
操作 | 快捷键 | 备注 |
---|---|---|
增加从当前行到文档末尾处的缩进层级 | >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 回溯学习记录。