backgraound-image

输入系统

基本原则:上限足够高,我可以不用,但你必须得有

Rime 输入法

中文输入法是一个痛点,目前尚无完美的方案解决。我曾尝试过微软输入法,手心输入法,搜狗输入法,百度输入法,讯飞输入法,rime 输入法等,都不算完美,基于以下几个方面:

  1. 隐私问题:输入法能够窥探到密码等个人隐私,尤其是类似搜狗这样的,每天输入的字符都会被严格记录
  2. 跨平台问题:mac,win,手机使得输入体验会有断层
  3. 定制:如今学习工作离不开打字,定制输入法让自己更舒服是一件终身大事

rime 输入法我是下了又删,下了又删,即使我有一些技术背景,但依然搞得有点头大。原因是他没有一套开箱即用的配置,丢给你一个毛坯房,挺痛苦的。但自从解除了四叶草输入方案,渐渐体会到 rime 输入引擎的强大之处。我并不打算高度定制 rime,但我选择的软件上限一定要高,因为我不能保证以后不会去折腾,不能保证未来没有大佬提供强大且友好的支持。这里给出 windows 小狼毫 Rime 配置的基础概览,最好直接照抄雾凇拼音方案,搞清楚每个文件的定义即可。

配置概览

rime 输入法的配置文件分为两处 (就是安装时你指定的两处位置):程序文件夹、用户文件夹右击托盘图标 可以选择 打开『程序文件夹』和『用户文件夹』

注意:在程序文件夹中的所有文件都不要动,虽然改动这里的文件也能达到自定义的效果,但每次更新都会被重置为默认设置。

我们采用“打补丁”的方式对程序文件夹中的文件进行修改,即在用户文件夹中创建 同名的 custom 文件。比如要对 default.yaml 文件修改,就创建 default.custom.yaml。

一般来说,我们需要创建的文件就三个

  1. default.custom.yaml 全局输入方案补丁,即定制内容对所有的输入方案都有效
  2. weasel.custom.yaml 全局皮肤样式补丁,分为样式和颜色两部分,对所有输入方案有效
  3. luna_pinyin.custom.yaml 朙月拼音 输入方案补丁,定制内容只对该方案有效,如果要修改其他方案,创建“方案名.custom.yaml”文件即可
  4. 方案名.schema.yaml 输入方案,直接覆盖程序文件夹下的该方案
  5. symbols.yaml 符号方案,对引入该文件的输入方案有效,用来定义按键输出的字符。因为是引用生效,所以自己的符号方案可以随意命名
  6. installation.yaml 定义用户资料同步的位置
  7. 词典名.dict.yaml 固定词典文件,对引入该文件的输入方案有效,用来定义能打出的词汇
  8. Custom_phrase.txt 用户词典文件,对所有输入方案有效,用来添加用户想添加的词汇
  9. 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 自动补全

自动补全一般分为三类:

  1. 根据笔记库自动补全
  2. 根据上下文自动补全
  3. 根据配置文件自动补全

这三类的定制性依次升高,实际上是精准补全 vs 全面补全的权衡。如果补全的来源是整个笔记库,提示的条数会在 9-10 条左右,随着笔记的增加,精准率会逐渐上升,同时给笔记软件带来的负荷也会上升。基本原理普遍是注册 codemirror 的扩展,稳定性会受到 ob 的影响。之前 ob 对 codemirror 做了一层封装,想要抹平 codemirror 升级带来对插件的影响,但由于很多是不可控的特性(比如把我最爱的光标美化给搞没了),此方案遂被放弃。

obsidian 里有极其多的自动补全方案,包括但不限于:

latex-suite 简单易学,前置条件是正则表达式。其目前分为三种模式 (看 issue 还在增加),options 是模式,t 代表文本模式,m 代表 latex 模式,r 代表正则模式,A 代表自动触发。$0 代表光标跳转的位置,[[0]] 代表正则表达式替换的位置。因此可实现:

  1. 正常编辑字符串/正则自动触发补全
  2. 正常编辑 tab 触发字符串/正则补全
  3. latex 模式自动补全,并带有预览框

vim 编辑跳转重构

ob 的编辑跳转比较羸弱,当碰到大文本量时(万字以上),很难优雅的跳转。于是可以采用 vim 模式进行增强,主要场景包括:

ob 里 vim 增强插件有:

注意: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" },
];

定位

|300

操作快捷键备注
下一个单词开头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 + ddown
向上滚半屏ctrl + uup
向下滚整屏ctrl + fforward
向上滚整屏ctrl + bbackward
让光标所处行在顶部zttop
让光标所处行在中间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

复制粘贴

操作快捷键备注
复制到系统剪贴板"+yshift + insert

折叠

操作快捷键备注
折叠zc
展开zo

可视模式与选区

v 可以进入可视模式

操作快捷键备注
激活面向字符的可视模式v
激活面向行的可视模式V
激活面向列块的可视模式ctrl + v
重选上次选区gv
切换选区活动端o
可视选择一个单词viw
选择标签内内容vitobsidian 内不适用
选区高亮的第一行(用于命令模式)`<
选区高亮的最后行(用于命令模式)`>
插入I A
修改整个可视列块c
修改整个可视列块即块后内容C
连接行J

Vim 的文本对象由两个字符组成,第一个字符永远是 i 或是 a。一 般以 i 开头的文本对象会选择分隔符内部的文本,而以 a 开头的文本对 象会选择包括分隔符在内的整个文本。为了便于记忆,可以把 i 成“inside”,而把 a 想成“around”或“all”。

|600

|600

记录与回放/宏录制

  1. "q{register}" 命令启动一次击键记录,结果保存到 {register} 指定的寄存器中。寄存器名可以用 a 到 z 中任一个字母表示。
  2. 输入你的命令。
  3. 键入 q (后面不用跟任何字符) 命令结束记录。

现在,你可以用 "@{register}" 命令执行这个宏。

快捷键管理

心得体会:别 tm 想着快捷键管理,常用的快捷键自然而然会记住,冲突了自然而然会替换,分配啥的随当时心意就好。

文本格式化

格式化的目的不仅仅是强迫症为了好看,更多的是为了文本一致性。一致的文本有利于检索。这里的一致性有很多可折腾的,但现在我并不想展开,它和整个笔记系统相关(这不是我故作高深,详情可以参考前端 linter,prettier 的爱恨情仇,每一个语句后面需不需要引号都能写篇长文论证。数以百计的 linter 规则,还有 git commit 自动化, html,css 等规则,足以说明健全的笔记系统,格式化的重要性足够重要)。只给出一致性带给我们的优势:给整个笔记库应用统一规则,给不同笔记应用不同的规则。这里的规则一方面限制我们书写笔记的自由度,使得文本检索,文本排版,文本索引,文本属性得以更好发挥,另一方面,给我们更大的自由度,即自动化。

目前 ob 的格式化插件也很多:

  1. platers/obsidian-linter: An Obsidian plugin that formats and styles your notes with a focus on configurability and extensibility. (github.com):强大,高度定制化,下限高,上限不低,能自定义运行命令。这个自定义运行命令是我加的,但现在看来多此一举,因为我可以用 quickadd 先运行 linter,再运行自定义命令,完全不用写一行代码。我人傻了。
  2. hipstersmoothie/obsidian-plugin-prettier: Format obsidian.md notes using prettier (github.com):功能羸弱
  3. cristianvasquez/obsidian-prettify: A markdown prettifier for obsidian (github.com):不再维护

其实文本格式化还有一个用途,就是 lint。比如笔记少些了 tag,哪个位置差点什么,能在 lint 时提醒自己。当然,这属于高度定制化功能,得 用 linter 插件写脚本。

版本管理

git 的重要性不言而喻,但非程序员很少有人能很好的利用起来这个强大的工具,具体这是个啥玩意儿,我又不是写教程,自己去学去。这里给出我的心得,git 教程应该不会有:

一般的 git 工作流:

  1. 创建新的分支 branch
  2. 创建提交 commit 保存你的笔记,使得该阶段可回溯
  3. 进行工作,完成工作,并提交 commit
  4. 合并分支到主分支

这种工作流往往会产生一些不必要的 commit 节点,使得 git 的历史是不整洁,不利于检查的。偶尔还需要 rebase,squash merge 等操作。

这里推荐一个新的工作流:

  1. 创建新的分支 branch
  2. 提交 commit 保存笔记,不必在意注释内容
  3. 重置 reset 会初始状态,重新 add 并 commit,写注释
  4. 合并分支到主分支
$ 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"

当然也可借助一些前端命令行工具应用一定的规则进行提交,比如 enhancestudy 等关键词,方便以后通过 git 回溯学习记录。

WD