首发于Vim
coc.nvim 插件体系 - 介绍

coc.nvim 插件体系 - 介绍

不仅提供了完整了 LSP 功能支持,甚至提供了加载插件的能力。

做为一个可以加载插件的 vim 插件,很多人不是太理解,本文来做一些简单的介绍。

起因

最主要的原因是仅有 LSP 支持无法实现像 VSCode 插件那样完整的功能。举一些例子:

  • VSCode 不同插件提供了许多配置项,大部分都是 server 使用的,但是也有一些是给 client 使用,如果不使用插件就需要在代码里对特定 server 进行不同适配,目前大部分 vim LSP 插件仅支持 server 使用的配置。
  • 某些功能需要客户端做一些特定的扩展,例如 java 的 jdt.ls 需要客户端支持 jdt 开头的 uri,使用 coc-java 插件用户只需要安装插件,无需手工配置。
  • 实现对于自定义请求/事件的处理。例如 coc-rls 监听 rls 的自定义事件来更新状态栏信息。
  • 问题修复。例如某些 server (css, json) 返回的补全项会包含非关键词,需要进行补全起始位置修复才能正确完成补全,通过 coc 插件的中间件方法可以修改响应或者请求,从而对于特定问题进行修复。

coc 插件的开发优势

总体来讲,如果你的需求相对比较简单,还是使用 viml 开发最为有效,如果你想弄一些复杂点的功能同时又对 javascript/typescript 比较了解,可以考虑基于 coc 开发。相比传统的 viml 以及 python 插件,coc 插件有自己的优势。

  • 优异的异步性能, coc 是独立于 vim 的 nodejs 进程,支持完整的异步通讯方法。可以用 viml 发送事件给 coc,也可以从 coc 发送事件给 vim 从而避免 request 所导致的进程阻塞。 coc 甚至实现了通过 viml 异步调用 coc 方法获取结果(例如: CocRequest()), 以及在 coc 内异步调用 vim 的方法获取结果(例如 coc 的 vim 补全源统一使用回调接受结果) , 这两种用法其它的 neovim 客户端暂时都不支持。相比于 python,javascipt 不仅有原生的 async await, 整个社区还有大量异步的轮子。
  • 性能优化,coc 提供了 document change 事件,无需额外事件以及传输消耗即可实时获取所有 缓冲区内容, 对于同时的连续 vim 事件(例如同时添加许多高亮区域),可以使用nvim.pauseNotification() 以及 nvim.resumeNotification() 批量化发送事件到 vim,从而避免连续事件导致 vim 反复重绘引起卡顿。
  • 基于 javascript 社区的模块,javascript 拥有最大的社区生态,你可以使用/借鉴已有的开发成果。
  • 使用 coc 提供的 API, coc 不仅提供了 languageclient 相关的接口帮助创建 LSP 插件,同时提供了许多其它方便的接口,例如通过 sources 模块可以轻松的添加补全 source,通过 list 模块可以创建高效且功能强大的列表。
  • 统一化 vim 和 neovim 适配,coc 新版在传输层对于 vim 做了 neovim 接口的适配,不再需要 vim 用户安装 vim-node-rpc 以及支持 python。除了部分无法在 vim 上支持的 neovim 特性,可以直接使用 node-client 提供的接口。对于有区别的 job 和 terminal 接口,coc 提供了 taskterminal 模块用于控制 (neo)vim 的 job 和 terminal。
  • 更加可靠的代码,基于 typescript 编写的代码相比与 viml 更加可靠而且可读, 同时 coc-tsserver 插件提供了 typscript 的完整支持,让编写 typescript 更加轻松。
  • 方便的配置和管理,跟 VSCode 插件一样,coc 插件可以在 package.json 中配置启动事件、自定义设置项、json scheme 关联等额外信息,coc-json 可以加载插件的自定义设置项从而对用户的配置文件进行补全和验证。安装 coc 插件仅需要 CocInstall 命令,管理 coc 插件可以通过 :CocList extensions 来操作。
  • 快速重新加载,无需重启 vim,使用命令 :CocRestart 可以重启 coc 服务,所有插件都会被重新加载,或者使用 g:coc_watch_extensions 配置同时安装 watchman 在插件代码变化后自动重新加载。
  • 调试代码,最简单的方式是在插件中使用 console.log, 输出内容会重定向到 coc.nvim 的日志,复杂一点的问题可以使用 Chrome 来调试,只需设置 let g:coc_node_args = ['--nolazy', '--inspect-brk=6045'], 然后 Chrome 打开 chrome://inspect 页面找到对应 target。

现有的非 LSP 插件

如果你不清楚 coc 插件是否可以取代某个插件,我个人建议看一下插件文档, 如果没有你特别想用的功能或者你要用的功能它没有那就没必要换(也可以提 feature request)。

一个简单的例子

例如我们想请求 lbdbq 里面的 email 列表用来做 email 补全。

首先找到配置的根目录,使用命令 :echo coc#util#get_config_home(), 通常结果是 ~/.vim, 在该目录下执行 mkdir coc-extensions && touch address.js 创建 coc-extensions 文件夹以及 address.js 文件。

在 address.js 文件内加入:

const {sources} = require('coc.nvim')
const {spawn} = require('child_process')
const readline = require('readline')

exports.activate = async context => {
  context.subscriptions.push(
    sources.createSource({
      // 唯一 id
      name: 'notmuch',
      // 用于 menu
      shortcut: 'address',
      // 指定 filetype
      filetypes: ['mail'],
      // 仅在触发条件满足时触发
      triggerOnly: true,
      priority: 99,
      triggerPatterns: [
        /^(Bcc|Cc|From|Reply-To|To):\s*/,
        /^(Bcc|Cc|From|Reply-To|To):.*,\s*/
      ],
      doComplete: async function(opt) {
        let matches = await getAddresses(opt.input)
        return {
          items: matches.map(m => {
            return {
              word: `${m[1]} <${m[0]}>`,
              abbr: `${m[0]} ${m[1]}`,
              filterText: `${m[0]} ${m[1]}`,
              menu: this.menu
            }
          })
        }
      }
    })
  )
}

async function getAddresses(input) {
  let result = []
  return new Promise((resolve, reject) => {
    const p = spawn('lbdbq', [input])
    const rl = readline.createInterface(p.stdout)
    p.on('error', reject)
    rl.on('line', line => {
      if (line.startsWith('lbdbq:')) return
      let [email, name] = line.split('\t')
      result.push([email, name])
    })
    rl.on('close', () => {
      resolve(result)
    })
  })
}

coc-extensions 目录下的所有 js 文件都会被 coc 当作插件加载,暂时无法支持 package.json 配置。


下一篇介绍 CocList, 一个和 fzf 一样快同时像 denite.nvim 一样强大的列表功能。

支持 coc.nvim 请扫描项目主页最下方二维码,同时获取更多支持者福利。

转发 CSDN 按侵权追究法律责任,其它情况随意。

编辑于 2019-05-13 18:27