coc.nvim
Version:
LSP based intellisense engine for neovim & vim8.
758 lines • 28 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const debounce_1 = tslib_1.__importDefault(require("debounce"));
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const events_1 = tslib_1.__importDefault(require("../events"));
const extensions_1 = tslib_1.__importDefault(require("../extensions"));
const util_1 = require("../util");
const workspace_1 = tslib_1.__importDefault(require("../workspace"));
const highligher_1 = tslib_1.__importDefault(require("../model/highligher"));
const configuration_1 = tslib_1.__importDefault(require("./configuration"));
const history_1 = tslib_1.__importDefault(require("./history"));
const mappings_1 = tslib_1.__importDefault(require("./mappings"));
const prompt_1 = tslib_1.__importDefault(require("./prompt"));
const commands_1 = tslib_1.__importDefault(require("./source/commands"));
const diagnostics_1 = tslib_1.__importDefault(require("./source/diagnostics"));
const extensions_2 = tslib_1.__importDefault(require("./source/extensions"));
const folders_1 = tslib_1.__importDefault(require("./source/folders"));
const links_1 = tslib_1.__importDefault(require("./source/links"));
const lists_1 = tslib_1.__importDefault(require("./source/lists"));
const location_1 = tslib_1.__importDefault(require("./source/location"));
const outline_1 = tslib_1.__importDefault(require("./source/outline"));
const output_1 = tslib_1.__importDefault(require("./source/output"));
const services_1 = tslib_1.__importDefault(require("./source/services"));
const sources_1 = tslib_1.__importDefault(require("./source/sources"));
const symbols_1 = tslib_1.__importDefault(require("./source/symbols"));
const actions_1 = tslib_1.__importDefault(require("./source/actions"));
const ui_1 = tslib_1.__importDefault(require("./ui"));
const worker_1 = tslib_1.__importDefault(require("./worker"));
const logger = require('../util/logger')('list-manager');
const mouseKeys = ['<LeftMouse>', '<LeftDrag>', '<LeftRelease>', '<2-LeftMouse>'];
class ListManager {
constructor() {
this.plugTs = 0;
this.disposables = [];
this.args = [];
this.listArgs = [];
this.listMap = new Map();
this.activated = false;
this.executing = false;
}
init(nvim) {
this.nvim = nvim;
this.config = new configuration_1.default();
this.prompt = new prompt_1.default(nvim, this.config);
this.history = new history_1.default(this);
this.mappings = new mappings_1.default(this, nvim, this.config);
this.worker = new worker_1.default(nvim, this);
this.ui = new ui_1.default(nvim, this.config);
events_1.default.on('VimResized', () => {
if (this.isActivated)
nvim.command('redraw!', true);
}, null, this.disposables);
events_1.default.on('InputChar', this.onInputChar, this, this.disposables);
events_1.default.on('FocusGained', debounce_1.default(async () => {
if (this.activated)
this.prompt.drawPrompt();
}, 100), null, this.disposables);
events_1.default.on('BufEnter', debounce_1.default(async () => {
let { bufnr } = this.ui;
if (!bufnr)
return;
if (!this.activated) {
this.ui.hide();
return;
}
let { isVim } = workspace_1.default;
let curr = await nvim.call('bufnr', '%');
if (curr == bufnr) {
this.prompt.start();
if (isVim)
nvim.command(`set t_ve=`, true);
}
else {
nvim.pauseNotification();
this.prompt.cancel();
await nvim.resumeNotification();
}
}, 100), null, this.disposables);
this.ui.onDidChangeLine(debounce_1.default(async () => {
if (!this.activated)
return;
let previewing = await nvim.call('coc#util#has_preview');
if (previewing)
await this.doAction('preview');
}, 100), null, this.disposables);
this.ui.onDidLineChange(debounce_1.default(async () => {
let { autoPreview } = this.listOptions;
if (!autoPreview || !this.activated)
return;
await this.doAction('preview');
}, 100), null, this.disposables);
this.ui.onDidChangeLine(this.resolveItem, this, this.disposables);
this.ui.onDidLineChange(this.resolveItem, this, this.disposables);
this.ui.onDidOpen(() => {
if (this.currList) {
if (typeof this.currList.doHighlight == 'function') {
this.currList.doHighlight();
}
}
}, null, this.disposables);
this.ui.onDidClose(async () => {
await this.cancel();
}, null, this.disposables);
this.ui.onDidChange(async () => {
if (this.activated) {
this.updateStatus();
}
this.prompt.drawPrompt();
}, null, this.disposables);
this.ui.onDidDoubleClick(async () => {
await this.doAction();
}, null, this.disposables);
this.worker.onDidChangeItems(async ({ items, highlights, reload, append }) => {
if (!this.activated)
return;
if (append) {
this.ui.addHighlights(highlights, true);
await this.ui.appendItems(items);
}
else {
this.ui.addHighlights(highlights);
await this.ui.drawItems(items, this.name, this.listOptions.position, reload);
}
}, null, this.disposables);
this.registerList(new links_1.default(nvim));
this.registerList(new location_1.default(nvim));
this.registerList(new symbols_1.default(nvim));
this.registerList(new outline_1.default(nvim));
this.registerList(new commands_1.default(nvim));
this.registerList(new extensions_2.default(nvim));
this.registerList(new diagnostics_1.default(nvim));
this.registerList(new sources_1.default(nvim));
this.registerList(new services_1.default(nvim));
this.registerList(new output_1.default(nvim));
this.registerList(new lists_1.default(nvim, this.listMap));
this.registerList(new folders_1.default(nvim));
this.registerList(new actions_1.default(nvim));
}
async start(args) {
if (this.activated)
return;
let res = this.parseArgs(args);
if (!res)
return;
this.args = args;
this.activated = true;
let { list, options, listArgs } = res;
try {
this.reset();
this.listOptions = options;
this.currList = list;
this.listArgs = listArgs;
this.cwd = workspace_1.default.cwd;
await this.getCharMap();
this.history.load();
this.window = await this.nvim.window;
this.savedHeight = await this.window.height;
this.prompt.start(options);
await this.worker.loadItems();
}
catch (e) {
await this.cancel();
let msg = e instanceof Error ? e.message : e.toString();
workspace_1.default.showMessage(`Error on "CocList ${list.name}": ${msg}`, 'error');
logger.error(e);
}
}
async resume() {
let { name, ui, currList, nvim } = this;
if (!currList)
return;
this.activated = true;
this.window = await nvim.window;
this.prompt.start();
await ui.resume(name, this.listOptions.position);
}
async doAction(name) {
let { currList } = this;
name = name || currList.defaultAction;
let action = currList.actions.find(o => o.name == name);
if (!action) {
workspace_1.default.showMessage(`Action ${name} not found`, 'error');
return;
}
let items = await this.ui.getItems();
if (items.length)
await this.doItemAction(items, action);
}
async previous() {
let { ui } = this;
let item = ui.getItem(-1);
if (!item)
return;
ui.index = ui.index - 1;
await this.doItemAction([item], this.defaultAction);
await ui.echoMessage(item);
}
async next() {
let { ui } = this;
let item = ui.getItem(1);
if (!item)
return;
ui.index = ui.index + 1;
await this.doItemAction([item], this.defaultAction);
await ui.echoMessage(item);
}
async cancel(close = true) {
let { nvim, ui, savedHeight } = this;
if (!this.activated) {
nvim.call('coc#list#stop_prompt', [], true);
return;
}
this.activated = false;
this.worker.stop();
this.history.add();
nvim.pauseNotification();
nvim.command('pclose', true);
this.prompt.cancel();
if (close) {
ui.hide();
if (this.window) {
nvim.call('coc#list#restore', [this.window.id, savedHeight], true);
}
}
await nvim.resumeNotification();
}
async switchMatcher() {
let { matcher, interactive } = this.listOptions;
if (interactive)
return;
const list = ['fuzzy', 'strict', 'regex'];
let idx = list.indexOf(matcher) + 1;
if (idx >= list.length)
idx = 0;
this.listOptions.matcher = list[idx];
this.prompt.matcher = list[idx];
await this.worker.drawItems();
}
async togglePreview() {
let { nvim } = this;
let has = await nvim.call('coc#list#has_preview');
if (has) {
await nvim.command('pclose');
await nvim.command('redraw');
}
else {
await this.doAction('preview');
}
}
async chooseAction() {
let { nvim, currList } = this;
if (!this.activated)
return;
let { actions, defaultAction } = currList;
let names = actions.map(o => o.name);
let idx = names.indexOf(defaultAction);
if (idx != -1) {
names.splice(idx, 1);
names.unshift(defaultAction);
}
let shortcuts = new Set();
let choices = [];
for (let name of names) {
let i = 0;
for (let ch of name) {
if (!shortcuts.has(ch)) {
shortcuts.add(ch);
choices.push(`${name.slice(0, i)}&${name.slice(i)}`);
break;
}
i++;
}
}
await nvim.call('coc#list#stop_prompt');
let n = await nvim.call('confirm', ['Choose action:', choices.join('\n')]);
await util_1.wait(10);
this.prompt.start();
if (n)
await this.doAction(names[n - 1]);
}
get name() {
let { currList } = this;
return currList ? currList.name : 'anonymous';
}
get list() {
return this.currList;
}
parseArgs(args) {
let options = [];
let interactive = false;
let autoPreview = false;
let numberSelect = false;
let name;
let input = '';
let matcher = 'fuzzy';
let position = 'bottom';
let listArgs = [];
let listOptions = [];
for (let arg of args) {
if (!name && arg.startsWith('-')) {
listOptions.push(arg);
}
else if (!name) {
if (!/^\w+$/.test(arg)) {
workspace_1.default.showMessage(`Invalid list option: "${arg}"`, 'error');
return null;
}
name = arg;
}
else {
listArgs.push(arg);
}
}
name = name || 'lists';
let config = workspace_1.default.getConfiguration(`list.source.${name}`);
if (!listOptions.length && !listArgs.length)
listOptions = config.get('defaultOptions', []);
if (!listArgs.length)
listArgs = config.get('defaultArgs', []);
for (let opt of listOptions) {
if (opt.startsWith('--input')) {
input = opt.slice(8);
}
else if (opt == '--number-select' || opt == '-N') {
numberSelect = true;
}
else if (opt == '--auto-preview' || opt == '-A') {
autoPreview = true;
}
else if (opt == '--regex' || opt == '-R') {
matcher = 'regex';
}
else if (opt == '--strict' || opt == '-S') {
matcher = 'strict';
}
else if (opt == '--interactive' || opt == '-I') {
interactive = true;
}
else if (opt == '--top') {
position = 'top';
}
else if (opt == '--tab') {
position = 'tab';
}
else if (opt == '--ignore-case' || opt == '--normal' || opt == '--no-sort') {
options.push(opt.slice(2));
}
else {
workspace_1.default.showMessage(`Invalid option "${opt}" of list`, 'error');
return null;
}
}
let list = this.listMap.get(name);
if (!list) {
workspace_1.default.showMessage(`List ${name} not found`, 'error');
return null;
}
if (interactive && !list.interactive) {
workspace_1.default.showMessage(`Interactive mode of "${name}" list not supported`, 'error');
return null;
}
return {
list,
listArgs,
options: {
numberSelect,
autoPreview,
input,
interactive,
matcher,
position,
ignorecase: options.indexOf('ignore-case') != -1 ? true : false,
mode: options.indexOf('normal') == -1 ? 'insert' : 'normal',
sort: options.indexOf('no-sort') == -1 ? true : false
},
};
}
updateStatus() {
let { ui, currList, activated, nvim } = this;
if (!activated)
return;
let buf = nvim.createBuffer(ui.bufnr);
let status = {
mode: this.prompt.mode.toUpperCase(),
args: this.args.join(' '),
name: currList.name,
total: this.worker.length,
cwd: this.cwd,
};
buf.setVar('list_status', status, true);
if (ui.window)
nvim.command('redraws', true);
}
async onInputChar(ch, charmod) {
let { mode } = this.prompt;
let mapped = this.charMap.get(ch);
let now = Date.now();
if (mapped == '<plug>' || now - this.plugTs < 2) {
this.plugTs = now;
return;
}
if (!ch)
return;
if (ch == '\x1b') {
await this.cancel();
return;
}
if (!this.activated) {
this.nvim.call('coc#list#stop_prompt', [], true);
return;
}
try {
if (mode == 'insert') {
await this.onInsertInput(ch, charmod);
}
else {
await this.onNormalInput(ch, charmod);
}
}
catch (e) {
workspace_1.default.showMessage(`Error on input ${ch}: ${e}`);
logger.error(e);
}
}
async onInsertInput(ch, charmod) {
let { nvim } = this;
let inserted = this.charMap.get(ch) || ch;
if (mouseKeys.indexOf(inserted) !== -1) {
await this.onMouseEvent(inserted);
return;
}
if (this.listOptions.numberSelect) {
let code = ch.charCodeAt(0);
if (code >= 48 && code <= 57) {
let n = Number(ch);
if (n == 0)
n = 10;
if (this.ui.length >= n) {
nvim.pauseNotification();
this.ui.setCursor(Number(ch), 0);
await nvim.resumeNotification();
await this.doAction();
}
return;
}
}
let done = await this.mappings.doInsertKeymap(inserted);
if (done || charmod || this.charMap.has(ch))
return;
for (let s of ch) {
let code = s.codePointAt(0);
if (code == 65533)
return;
// exclude control characer
if (code < 32 || code >= 127 && code <= 159)
return;
this.prompt.insertCharacter(s);
}
}
async onNormalInput(ch, _charmod) {
let inserted = this.charMap.get(ch) || ch;
if (mouseKeys.indexOf(inserted) !== -1) {
await this.onMouseEvent(inserted);
return;
}
let done = await this.mappings.doNormalKeymap(inserted);
if (!done)
await this.feedkeys(inserted);
}
onMouseEvent(key) {
switch (key) {
case '<LeftMouse>':
return this.ui.onMouse('mouseDown');
case '<LeftDrag>':
return this.ui.onMouse('mouseDrag');
case '<LeftRelease>':
return this.ui.onMouse('mouseUp');
case '<2-LeftMouse>':
return this.ui.onMouse('doubleClick');
}
}
async feedkeys(key) {
let { nvim } = this;
key = key.startsWith('<') && key.endsWith('>') ? `\\${key}` : key;
await nvim.call('coc#list#stop_prompt', [1]);
await nvim.eval(`feedkeys("${key}")`);
this.prompt.start();
}
async command(command) {
let { nvim } = this;
await nvim.call('coc#list#stop_prompt', [1]);
await nvim.command(command);
this.prompt.start();
}
async normal(command, bang = true) {
let { nvim } = this;
await nvim.call('coc#list#stop_prompt', [1]);
await nvim.command(`normal${bang ? '!' : ''} ${command}`);
this.prompt.start();
}
async call(fname) {
if (!this.currList || !this.window)
return;
await this.nvim.call('coc#list#stop_prompt', []);
let buf = await this.window.buffer;
let targets = await this.ui.getItems();
let context = {
name: this.currList.name,
args: this.listArgs,
input: this.prompt.input,
winid: this.window.id,
bufnr: buf.id,
targets
};
let res = await this.nvim.call(fname, [context]);
this.prompt.start();
return res;
}
async showHelp() {
// echo help
await this.cancel();
let { list, nvim } = this;
if (!list)
return;
let previewHeight = await nvim.eval('&previewheight');
nvim.pauseNotification();
nvim.command(`belowright ${previewHeight}sp +setl\\ previewwindow [LIST HELP]`, true);
nvim.command('setl nobuflisted noswapfile buftype=nofile bufhidden=wipe', true);
await nvim.resumeNotification();
let hasOptions = list.options && list.options.length;
let buf = await nvim.buffer;
let highligher = new highligher_1.default();
highligher.addLine('NAME', 'Label');
highligher.addLine(` ${list.name} - ${list.description || ''}\n`);
highligher.addLine('SYNOPSIS', 'Label');
highligher.addLine(` :CocList [LIST OPTIONS] ${list.name}${hasOptions ? ' [ARGUMENTS]' : ''}\n`);
if (list.detail) {
highligher.addLine('DESCRIPTION', 'Label');
let lines = list.detail.split('\n').map(s => ' ' + s);
highligher.addLine(lines + '\n');
}
if (hasOptions) {
highligher.addLine('ARGUMENTS', 'Label');
highligher.addLine('');
for (let opt of list.options) {
highligher.addLine(opt.name, 'Special');
highligher.addLine(` ${opt.description}`);
}
highligher.addLine('');
}
let config = workspace_1.default.getConfiguration(`list.source.${list.name}`);
if (Object.keys(config).length) {
highligher.addLine('CONFIGURATIONS', 'Label');
highligher.addLine('');
let props = {};
extensions_1.default.all.forEach(extension => {
let { packageJSON } = extension;
let { contributes } = packageJSON;
if (!contributes)
return;
let { configuration } = contributes;
if (configuration) {
let { properties } = configuration;
if (properties) {
for (let key of Object.keys(properties)) {
props[key] = properties[key];
}
}
}
});
for (let key of Object.keys(config)) {
let val = config[key];
let name = `list.source.${list.name}.${key}`;
let description = props[name] && props[name].description ? props[name].description : key;
highligher.addLine(` "${name}"`, 'MoreMsg');
highligher.addText(` - ${description}, current value: `);
highligher.addText(JSON.stringify(val), 'Special');
}
highligher.addLine('');
}
highligher.addLine('ACTIONS', 'Label');
highligher.addLine(` ${list.actions.map(o => o.name).join(', ')}`);
highligher.addLine('');
highligher.addLine(`see ':h coc-list--options' for available list options.`, 'Comment');
nvim.pauseNotification();
highligher.render(buf, 0, -1);
nvim.command('setl nomod', true);
nvim.command('setl nomodifiable', true);
nvim.command('normal! gg', true);
nvim.command('nnoremap q :bd!<CR>', true);
await nvim.resumeNotification();
}
get context() {
return {
options: this.listOptions,
args: this.listArgs,
input: this.prompt.input,
window: this.window,
listWindow: this.ui.window,
cwd: this.cwd
};
}
registerList(list) {
const { name } = list;
let exists = this.listMap.get(name);
if (this.listMap.has(name)) {
if (exists) {
if (typeof exists.dispose == 'function') {
exists.dispose();
}
this.listMap.delete(name);
}
workspace_1.default.showMessage(`list "${name}" recreated.`);
}
this.listMap.set(name, list);
extensions_1.default.addSchemeProperty(`list.source.${name}.defaultOptions`, {
type: 'array',
default: list.interactive ? ['--interactive'] : [],
description: `Default list options of "${name}" list, only used when both list option and argument are empty.`,
uniqueItems: true,
items: {
type: 'string',
enum: ['--top', '--normal', '--no-sort', '--input', '--tab',
'--strict', '--regex', '--ignore-case', '--number-select',
'--interactive', '--auto-preview']
}
});
extensions_1.default.addSchemeProperty(`list.source.${name}.defaultArgs`, {
type: 'array',
default: [],
description: `Default argument list of "${name}" list, only used when list argument is empty.`,
uniqueItems: true,
items: { type: 'string' }
});
return vscode_languageserver_protocol_1.Disposable.create(() => {
if (typeof list.dispose == 'function') {
list.dispose();
}
this.listMap.delete(name);
});
}
get names() {
return Array.from(this.listMap.keys());
}
toggleMode() {
let { mode } = this.prompt;
this.prompt.mode = mode == 'normal' ? 'insert' : 'normal';
this.updateStatus();
}
getConfig(key, defaultValue) {
return this.config.get(key, defaultValue);
}
get isActivated() {
return this.activated;
}
stop() {
this.worker.stop();
}
reset() {
this.window = null;
this.listOptions = null;
this.prompt.reset();
this.worker.stop();
this.ui.reset();
}
dispose() {
if (this.config) {
this.config.dispose();
}
util_1.disposeAll(this.disposables);
}
async getCharMap() {
if (this.charMap)
return;
this.charMap = new Map();
let chars = await this.nvim.call('coc#list#get_chars');
Object.keys(chars).forEach(key => {
this.charMap.set(chars[key], key);
});
return;
}
async doItemAction(items, action) {
if (this.executing)
return;
this.executing = true;
let { nvim } = this;
let shouldCancel = action.persist !== true && action.name != 'preview';
try {
if (shouldCancel) {
await this.cancel();
}
else if (action.name != 'preview') {
await nvim.call('coc#list#stop_prompt');
}
if (!shouldCancel && !this.isActivated)
return;
if (action.multiple) {
await Promise.resolve(action.execute(items, this.context));
}
else if (action.parallel) {
await Promise.all(items.map(item => {
return Promise.resolve(action.execute(item, this.context));
}));
}
else {
for (let item of items) {
await Promise.resolve(action.execute(item, this.context));
}
}
if (!shouldCancel) {
if (!this.isActivated) {
this.nvim.command('pclose', true);
return;
}
nvim.pauseNotification();
if (action.name != 'preview') {
this.prompt.start();
}
this.ui.restoreWindow();
nvim.resumeNotification(false, true).logError();
if (action.reload)
await this.worker.loadItems(true);
}
}
catch (e) {
// tslint:disable-next-line: no-console
console.error(e);
if (!shouldCancel && this.activated) {
this.prompt.start();
}
}
this.executing = false;
}
async resolveItem() {
if (!this.activated)
return;
let index = this.ui.index;
let item = this.ui.getItem(0);
if (!item || item.resolved)
return;
let { list } = this;
if (typeof list.resolveItem == 'function') {
let resolved = await list.resolveItem(item);
if (resolved && index == this.ui.index) {
await this.ui.updateItem(resolved, index);
}
}
}
get defaultAction() {
let { currList } = this;
let { defaultAction } = currList;
return currList.actions.find(o => o.name == defaultAction);
}
}
exports.ListManager = ListManager;
exports.default = new ListManager();
//# sourceMappingURL=manager.js.map