UNPKG

coc.nvim

Version:

LSP based intellisense engine for neovim & vim8.

471 lines 17.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol"); const events_1 = tslib_1.__importDefault(require("../events")); const util_1 = require("../util"); const workspace_1 = tslib_1.__importDefault(require("../workspace")); const debounce = require("debounce"); const logger = require('../util/logger')('list-ui'); class ListUI { constructor(nvim, config) { this.nvim = nvim; this.config = config; this.newTab = false; this._bufnr = 0; this.currIndex = 0; this.highlights = []; this.items = []; this.disposables = []; this.selected = new Set(); this.creating = false; this._onDidChangeLine = new vscode_languageserver_protocol_1.Emitter(); this._onDidOpen = new vscode_languageserver_protocol_1.Emitter(); this._onDidClose = new vscode_languageserver_protocol_1.Emitter(); this._onDidChange = new vscode_languageserver_protocol_1.Emitter(); this._onDidLineChange = new vscode_languageserver_protocol_1.Emitter(); this._onDoubleClick = new vscode_languageserver_protocol_1.Emitter(); this.onDidChangeLine = this._onDidChangeLine.event; this.onDidLineChange = this._onDidLineChange.event; this.onDidOpen = this._onDidOpen.event; this.onDidClose = this._onDidClose.event; this.onDidChange = this._onDidChange.event; this.onDidDoubleClick = this._onDoubleClick.event; let signText = config.get('selectedSignText', '*'); nvim.command(`sign define CocSelected text=${signText} texthl=CocSelectedText linehl=CocSelectedLine`, true); this.signOffset = config.get('signOffset'); events_1.default.on('BufUnload', async (bufnr) => { if (bufnr == this.bufnr) { this._bufnr = 0; this.window = null; this._onDidClose.fire(bufnr); } }, null, this.disposables); let timer; events_1.default.on('CursorMoved', async (bufnr, cursor) => { if (timer) clearTimeout(timer); if (bufnr != this.bufnr) return; let lnum = cursor[0]; if (this.currIndex + 1 != lnum) { this.currIndex = lnum - 1; this._onDidChangeLine.fire(lnum); } }, null, this.disposables); events_1.default.on('CursorMoved', debounce(async (bufnr) => { if (bufnr != this.bufnr) return; let [start, end] = await nvim.eval('[line("w0"),line("w$")]'); if (end < 500) return; nvim.pauseNotification(); this.doHighlight(start - 1, end); nvim.command('redraw', true); await nvim.resumeNotification(false, true); }, 100)); } set index(n) { if (n < 0 || n >= this.items.length) return; this.currIndex = n; if (this.window) { let { nvim } = this; nvim.pauseNotification(); this.setCursor(n + 1, 0); nvim.command('redraw', true); nvim.resumeNotification(false, true).catch(_e => { // noop }); } } get index() { return this.currIndex; } getItem(delta) { let { currIndex } = this; return this.items[currIndex + delta]; } get item() { let { window } = this; if (!window) return Promise.resolve(null); return window.cursor.then(cursor => { this.currIndex = cursor[0] - 1; return this.items[this.currIndex]; }, _e => { return null; }); } async echoMessage(item) { if (this.bufnr) return; let { items } = this; let idx = items.indexOf(item); let msg = `[${idx + 1}/${items.length}] ${item.label || ''}`; this.nvim.callTimer('coc#util#echo_lines', [[msg]], true); } async updateItem(item, index) { if (!this.bufnr || workspace_1.default.bufnr != this.bufnr) return; let obj = Object.assign({ resolved: true }, item); if (index < this.length) { this.items[index] = obj; let { nvim } = this; nvim.pauseNotification(); nvim.command('setl modifiable', true); nvim.call('setline', [index + 1, obj.label], true); nvim.command('setl nomodifiable', true); await nvim.resumeNotification(); } } async getItems() { if (this.length == 0) return []; let mode = await this.nvim.call('mode'); if (mode == 'v' || mode == 'V') { let [start, end] = await this.getSelectedRange(); let res = []; for (let i = start; i <= end; i++) { res.push(this.items[i - 1]); } return res; } let { selectedItems } = this; if (selectedItems.length) return selectedItems; let item = await this.item; return item == null ? [] : [item]; } async onMouse(event) { let { nvim, window } = this; let winid = await nvim.getVvar('mouse_winid'); if (!window) return; let lnum = await nvim.getVvar('mouse_lnum'); let col = await nvim.getVvar('mouse_col'); if (event == 'mouseDown') { this.mouseDown = { winid, lnum, col, current: winid == window.id }; return; } let current = winid == window.id; if (current && event == 'doubleClick') { this.setCursor(lnum, 0); this._onDoubleClick.fire(); } if (!this.mouseDown || this.mouseDown.winid != this.mouseDown.winid) return; if (current && event == 'mouseDrag') { await this.selectLines(this.mouseDown.lnum, lnum); } else if (current && event == 'mouseUp') { if (this.mouseDown.lnum == lnum) { nvim.pauseNotification(); this.clearSelection(); this.setCursor(lnum, 0); nvim.command('redraw', true); await nvim.resumeNotification(); } else { await this.selectLines(this.mouseDown.lnum, lnum); } } else if (!current && event == 'mouseUp') { nvim.pauseNotification(); nvim.call('win_gotoid', winid, true); nvim.call('cursor', [lnum, col], true); await nvim.resumeNotification(); } } reset() { this.items = []; this.mouseDown = null; this.selected = new Set(); this._bufnr = 0; this.window = null; } hide() { let { bufnr, nvim } = this; if (bufnr) { this._bufnr = 0; nvim.command(`silent! bd! ${bufnr}`, true); } } async resume(name, position) { let { items, selected, nvim, signOffset } = this; await this.drawItems(items, name, position, true); if (selected.size > 0 && this.bufnr) { nvim.pauseNotification(); for (let lnum of selected) { nvim.command(`sign place ${signOffset + lnum} line=${lnum} name=CocSelected buffer=${this.bufnr}`, true); } await nvim.resumeNotification(); } } async toggleSelection() { let { nvim, selected, signOffset, bufnr } = this; if (workspace_1.default.bufnr != bufnr) return; let lnum = await nvim.call('line', '.'); let mode = await nvim.call('mode'); if (mode == 'v' || mode == 'V') { let [start, end] = await this.getSelectedRange(); let exists = selected.has(start); let reverse = start > end; if (reverse) [start, end] = [end, start]; for (let i = start; i <= end; i++) { if (!exists) { selected.add(i); nvim.command(`sign place ${signOffset + i} line=${i} name=CocSelected buffer=${bufnr}`, true); } else { selected.delete(i); nvim.command(`sign unplace ${signOffset + i} buffer=${bufnr}`, true); } } this.setCursor(end, 0); nvim.command('redraw', true); await nvim.resumeNotification(); return; } let exists = selected.has(lnum); nvim.pauseNotification(); if (exists) { selected.delete(lnum); nvim.command(`sign unplace ${signOffset + lnum} buffer=${bufnr}`, true); } else { selected.add(lnum); nvim.command(`sign place ${signOffset + lnum} line=${lnum} name=CocSelected buffer=${bufnr}`, true); } this.setCursor(lnum + 1, 0); nvim.command('redraw', true); await nvim.resumeNotification(); } async selectLines(start, end) { let { nvim, signOffset, bufnr, length } = this; this.clearSelection(); let { selected } = this; nvim.pauseNotification(); let reverse = start > end; if (reverse) [start, end] = [end, start]; for (let i = start; i <= end; i++) { if (i > length) break; selected.add(i); nvim.command(`sign place ${signOffset + i} line=${i} name=CocSelected buffer=${bufnr}`, true); } this.setCursor(end, 0); nvim.command('redraw', true); await nvim.resumeNotification(); } async selectAll() { let { length } = this; if (length == 0) return; await this.selectLines(1, length); } clearSelection() { let { selected, nvim, signOffset, bufnr } = this; if (!bufnr) return; if (selected.size > 0) { let signIds = []; for (let lnum of selected) { signIds.push(signOffset + lnum); } nvim.call('coc#util#unplace_signs', [bufnr, signIds], true); this.selected = new Set(); } } get shown() { return this._bufnr != 0; } get bufnr() { return this._bufnr; } get ready() { if (this._bufnr) return Promise.resolve(); if (this.creating) { return new Promise(resolve => { let disposable = this.onDidOpen(() => { disposable.dispose(); resolve(); }); }); } } async drawItems(items, name, position = 'bottom', reload = false) { let { bufnr, config, nvim } = this; this.newTab = position == 'tab'; let maxHeight = config.get('maxHeight', 12); let height = Math.max(1, Math.min(items.length, maxHeight)); let limitLines = config.get('limitLines', 30000); let curr = this.items[this.index]; this.items = items.slice(0, limitLines); if (bufnr == 0 && !this.creating) { this.creating = true; let [bufnr, winid] = await nvim.call('coc#list#create', [position, height, name]); this._bufnr = bufnr; this.window = nvim.createWindow(winid); this.height = height; this._onDidOpen.fire(this.bufnr); this.creating = false; } else { await this.ready; } let lines = this.items.map(item => item.label); this.clearSelection(); await this.setLines(lines, false, reload ? this.currIndex : 0); let item = this.items[this.index] || { label: '' }; if (!curr || curr.label != item.label) { this._onDidLineChange.fire(this.index + 1); } } async appendItems(items) { let { config } = this; let limitLines = config.get('limitLines', 1000); let curr = this.items.length; if (curr >= limitLines) { this._onDidChange.fire(); return; } let max = limitLines - curr; let append = items.slice(0, max); this.items = this.items.concat(append); if (this.creating) return; await this.setLines(append.map(item => item.label), curr > 0, this.currIndex); } async setLines(lines, append = false, index) { let { nvim, bufnr, window, config } = this; if (!bufnr || !window) return; let resize = !this.newTab && config.get('autoResize', true); let buf = nvim.createBuffer(bufnr); nvim.pauseNotification(); nvim.call('win_gotoid', window.id, true); if (!append) { nvim.call('clearmatches', [], true); } if (resize) { let maxHeight = config.get('maxHeight', 12); let height = Math.max(1, Math.min(this.items.length, maxHeight)); this.height = height; nvim.call('coc#list#set_height', [height], true); } if (!append) { if (!lines.length) { lines = ['Press ? on normal mode to get help.']; nvim.call('matchaddpos', ['Comment', [[1]], 99], true); } } nvim.command('setl modifiable', true); if (workspace_1.default.isVim) { nvim.call('coc#list#setlines', [lines, append], true); } else { buf.setLines(lines, { start: append ? -1 : 0, end: -1, strictIndexing: false }, true); } nvim.command('setl nomodifiable', true); if (!append && index == 0) { this.doHighlight(0, 300); } else { let height = this.newTab ? workspace_1.default.env.lines : this.height; this.doHighlight(Math.max(0, index - height), Math.min(index + height + 1, this.length - 1)); } if (!append) window.notify('nvim_win_set_cursor', [[index + 1, 0]]); this._onDidChange.fire(); if (workspace_1.default.isVim) nvim.command('redraw', true); nvim.resumeNotification(false, true).catch(_e => { // noop }); } restoreWindow() { if (this.newTab) return; let { window, height } = this; if (window && height) { this.nvim.call('coc#list#restore', [window.id, height], true); } } dispose() { util_1.disposeAll(this.disposables); } get length() { return this.items.length; } get selectedItems() { let { selected, items } = this; let res = []; for (let i of selected) { if (items[i - 1]) res.push(items[i - 1]); } return res; } doHighlight(start, end) { let { nvim } = workspace_1.default; let { highlights, items } = this; for (let i = start; i <= Math.min(end, items.length - 1); i++) { let { ansiHighlights } = items[i]; let highlight = highlights[i]; if (ansiHighlights) { for (let hi of ansiHighlights) { let { span, hlGroup } = hi; nvim.call('matchaddpos', [hlGroup, [[i + 1, span[0] + 1, span[1] - span[0]]], 9], true); } } if (highlight) { let { spans, hlGroup } = highlight; for (let span of spans) { nvim.call('matchaddpos', [hlGroup || 'Search', [[i + 1, span[0] + 1, span[1] - span[0]]], 11], true); } } } } setCursor(lnum, col) { let { window, bufnr, items } = this; let max = items.length == 0 ? 1 : items.length; if (!bufnr || !window || lnum > max) return; window.notify('nvim_win_set_cursor', [[lnum, col]]); if (this.currIndex + 1 != lnum) { this.currIndex = lnum - 1; this._onDidChangeLine.fire(lnum); } } addHighlights(highlights, append = false) { let limitLines = this.config.get('limitLines', 1000); if (!append) { this.highlights = highlights.slice(0, limitLines); } else { if (this.highlights.length < limitLines) { this.highlights = this.highlights.concat(highlights.slice(0, limitLines - this.highlights.length)); } } } async getSelectedRange() { let { nvim } = this; await nvim.call('coc#list#stop_prompt'); await nvim.eval('feedkeys("\\<esc>", "in")'); let [, start] = await nvim.call('getpos', "'<"); let [, end] = await nvim.call('getpos', "'>"); if (start > end) { [start, end] = [end, start]; } let method = workspace_1.default.isVim ? 'coc#list#prompt_start' : 'coc#list#start_prompt'; this.nvim.call(method, [], true); return [start, end]; } } exports.default = ListUI; //# sourceMappingURL=ui.js.map