'use strict' import { Neovim } from '@chemzqm/neovim' import { v1 as uuid } from 'uuid' import { Disposable } from 'vscode-languageserver-protocol' import { KeymapOption } from '../types' import { getKeymapModifier, MapMode } from '../util' import Documents from './documents' const logger = require('../util/logger')('core-keymaps') export default class Keymaps { private readonly keymaps: Map = new Map() private nvim: Neovim constructor(private documents: Documents) { } public attach(nvim: Neovim): void { this.nvim = nvim } public async doKeymap(key: string, defaultReturn = '', pressed?: string): Promise { let keymap = this.keymaps.get(key) if (!keymap) { logger.error(`keymap for ${key} not found`) if (pressed) this.nvim.command(`silent! unmap ${pressed.startsWith('{') && pressed.endsWith('}') ? `<${pressed.slice(1, -1)}>` : pressed}`, true) return defaultReturn } let [fn, repeat] = keymap let res = await Promise.resolve(fn()) if (repeat) await this.nvim.command(`silent! call repeat#set("\\(coc-${key})", -1)`) return res ?? defaultReturn } /** * Register (coc-${key}) key mapping. */ public registerKeymap(modes: MapMode[], key: string, fn: Function, opts: Partial = {}): Disposable { if (!key) throw new Error(`Invalid key ${key} of registerKeymap`) if (this.keymaps.has(key)) throw new Error(`${key} already exists.`) opts = Object.assign({ sync: true, cancel: true, silent: true, repeat: false }, opts) let { nvim } = this this.keymaps.set(key, [fn, !!opts.repeat]) let method = opts.sync ? 'request' : 'notify' let silent = opts.silent ? '' : '' for (let m of modes) { if (m == 'i') { nvim.command(`inoremap ${silent} (coc-${key}) coc#_insert_key('${method}', '${key}', ${opts.cancel ? 1 : 0})`, true) } else { let modify = getKeymapModifier(m) nvim.command(`${m}noremap ${silent} (coc-${key}) :${modify}call coc#rpc#${method}('doKeymap', ['${key}'])`, true) } } return Disposable.create(() => { this.keymaps.delete(key) for (let m of modes) { nvim.command(`${m}unmap (coc-${key})`, true) } }) } public registerExprKeymap(mode: 'i' | 'n' | 'v' | 's' | 'x', key: string, fn: Function, buffer = false): Disposable { let id = `${mode}${global.Buffer.from(key).toString('base64')}${buffer ? '1' : '0'}` let { nvim } = this this.keymaps.set(id, [fn, false]) if (mode == 'i') { nvim.command(`inoremap ${buffer ? '' : ''} ${key} coc#_insert_key('request', '${id}')`, true) } else { nvim.command(`${mode}noremap ${buffer ? '' : ''} ${key} coc#rpc#request('doKeymap', ['${id}'])`, true) } return Disposable.create(() => { this.keymaps.delete(id) nvim.command(`${mode}unmap ${buffer ? '' : ''} ${key}`, true) }) } public registerLocalKeymap(mode: 'n' | 'v' | 's' | 'x', key: string, fn: Function, notify = false): Disposable { let id = uuid() let { nvim } = this let bufnr = this.documents.bufnr this.keymaps.set(id, [fn, false]) let method = notify ? 'notify' : 'request' let modify = getKeymapModifier(mode) // neoivm's bug '<' can't be used. let escaped = key.startsWith('<') && key.endsWith('>') ? `{${key.slice(1, -1)}}` : key if (this.nvim.hasFunction('nvim_buf_set_keymap') && !global.hasOwnProperty('__TEST__')) { nvim.call('nvim_buf_set_keymap', [0, mode, key, `:${modify}call coc#rpc#${method}('doKeymap', ['${id}', '', '${escaped}'])`, { silent: true, nowait: true }], true) } else { let cmd = `${mode}noremap ${key} :${modify}call coc#rpc#${method}('doKeymap', ['${id}', '', '${escaped}'])` nvim.command(cmd, true) } return Disposable.create(() => { this.keymaps.delete(id) nvim.call('coc#compat#buf_del_keymap', [bufnr, mode, key], true) }) } }