UNPKG

coc.nvim

Version:

LSP based intellisense engine for neovim & vim8.

598 lines 24.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol"); const vscode_uri_1 = require("vscode-uri"); const events_1 = tslib_1.__importDefault(require("../events")); const floatFactory_1 = tslib_1.__importDefault(require("../model/floatFactory")); const util_1 = require("../util"); const position_1 = require("../util/position"); const workspace_1 = tslib_1.__importDefault(require("../workspace")); const buffer_1 = require("./buffer"); const collection_1 = tslib_1.__importDefault(require("./collection")); const util_2 = require("./util"); const logger = require('../util/logger')('diagnostic-manager'); class DiagnosticManager { constructor() { this.enabled = true; this.buffers = []; this.collections = []; this.disposables = []; this.lastMessage = ''; } init() { this.setConfiguration(); let { nvim } = workspace_1.default; let { maxWindowHeight } = this.config; this.floatFactory = new floatFactory_1.default(nvim, workspace_1.default.env, false, maxWindowHeight); this.disposables.push(vscode_languageserver_protocol_1.Disposable.create(() => { if (this.timer) clearTimeout(this.timer); })); let moved = false; events_1.default.on('CursorMoved', async () => { moved = true; if (this.timer) clearTimeout(this.timer); this.timer = setTimeout(async () => { if (this.config.enableMessage != 'always') return; if (this.config.messageTarget == 'float') return; await this.echoMessage(true); }, 500); }, null, this.disposables); if (this.config.messageTarget == 'float') { this.disposables.push(workspace_1.default.registerAutocmd({ event: 'CursorHold', request: true, callback: async () => { if (!moved) return; moved = false; let popup = await nvim.eval('get(w:, "float", 0)'); if (popup) return; await this.echoMessage(true); } })); } events_1.default.on('InsertEnter', async () => { this.floatFactory.close(); if (this.timer) clearTimeout(this.timer); }, null, this.disposables); events_1.default.on('InsertLeave', async (bufnr) => { this.floatFactory.close(); let doc = workspace_1.default.getDocument(bufnr); if (!doc || !this.shouldValidate(doc)) return; let { refreshOnInsertMode, refreshAfterSave } = this.config; if (!refreshOnInsertMode && !refreshAfterSave) { await util_1.wait(500); this.refreshBuffer(doc.uri); } }, null, this.disposables); events_1.default.on('BufEnter', async (bufnr) => { if (this.timer) clearTimeout(this.timer); if (!this.config || !this.enabled || !this.config.locationlist) return; let doc = workspace_1.default.getDocument(bufnr); if (!this.shouldValidate(doc) || doc.bufnr != bufnr) return; let refreshed = this.refreshBuffer(doc.uri); if (!refreshed) { let winid = await nvim.call('win_getid'); let curr = await nvim.call('getloclist', [winid, { title: 1 }]); if ((curr.title && curr.title.indexOf('Diagnostics of coc') != -1)) { nvim.call('setloclist', [winid, [], 'f'], true); } } }, null, this.disposables); events_1.default.on('BufWritePost', async (bufnr) => { let buf = this.buffers.find(buf => buf.bufnr == bufnr); if (buf) await buf.checkSigns(); await util_1.wait(100); if (this.config.refreshAfterSave) { this.refreshBuffer(buf.uri); } }, null, this.disposables); workspace_1.default.onDidChangeConfiguration(async (e) => { this.setConfiguration(e); }, null, this.disposables); // create buffers for (let doc of workspace_1.default.documents) { this.createDiagnosticBuffer(doc); } workspace_1.default.onDidOpenTextDocument(textDocument => { let doc = workspace_1.default.getDocument(textDocument.uri); this.createDiagnosticBuffer(doc); }, null, this.disposables); workspace_1.default.onDidCloseTextDocument(async ({ uri }) => { let doc = workspace_1.default.getDocument(uri); if (!doc) return; let { bufnr } = doc; let idx = this.buffers.findIndex(buf => buf.bufnr == bufnr); if (idx == -1) return; let buf = this.buffers[idx]; buf.dispose(); this.buffers.splice(idx, 1); for (let collection of this.collections) { collection.delete(buf.uri); } await buf.clear(); }, null, this.disposables); this.setConfigurationErrors(true); workspace_1.default.configurations.onError(async () => { this.setConfigurationErrors(); }, null, this.disposables); let { errorSign, warningSign, infoSign, hintSign } = this.config; nvim.pauseNotification(); nvim.command(`sign define CocError text=${errorSign} linehl=CocErrorLine texthl=CocErrorSign`, true); nvim.command(`sign define CocWarning text=${warningSign} linehl=CocWarningLine texthl=CocWarningSign`, true); nvim.command(`sign define CocInfo text=${infoSign} linehl=CocInfoLine texthl=CocInfoSign`, true); nvim.command(`sign define CocHint text=${hintSign} linehl=CocHintLine texthl=CocHintSign`, true); if (this.config.virtualText && workspace_1.default.isNvim) { nvim.call('coc#util#init_virtual_hl', [], true); } nvim.resumeNotification(false, true).catch(_e => { // noop }); } createDiagnosticBuffer(doc) { if (!this.shouldValidate(doc)) return; let idx = this.buffers.findIndex(b => b.bufnr == doc.bufnr); if (idx == -1) { let buf = new buffer_1.DiagnosticBuffer(doc, this.config); this.buffers.push(buf); buf.onDidRefresh(() => { if (workspace_1.default.insertMode) return; this.echoMessage(true).catch(_e => { // noop }); }); } } setConfigurationErrors(init) { let collections = this.collections; let collection = collections.find(o => o.name == 'config'); if (!collection) { collection = this.create('config'); } else { collection.clear(); } let { errorItems } = workspace_1.default.configurations; if (errorItems && errorItems.length) { if (init) workspace_1.default.showMessage(`settings file parse error, run ':CocList diagnostics'`, 'error'); let entries = new Map(); for (let item of errorItems) { let { uri } = item.location; let diagnostics = entries.get(uri) || []; diagnostics.push(vscode_languageserver_protocol_1.Diagnostic.create(item.location.range, item.message, vscode_languageserver_protocol_1.DiagnosticSeverity.Error)); entries.set(uri, diagnostics); } collection.set(Array.from(entries)); } } /** * Create collection by name */ create(name) { let collection = new collection_1.default(name); this.collections.push(collection); let disposable = collection.onDidDiagnosticsChange(async (uri) => { if (this.config.refreshAfterSave) return; this.refreshBuffer(uri); }); let dispose = collection.onDidDiagnosticsClear(uris => { for (let uri of uris) { this.refreshBuffer(uri); } }); collection.onDispose(() => { disposable.dispose(); dispose.dispose(); let idx = this.collections.findIndex(o => o == collection); if (idx !== -1) this.collections.splice(idx, 1); collection.forEach((uri, diagnostics) => { if (diagnostics && diagnostics.length) this.refreshBuffer(uri); }); }); return collection; } /** * Get diagnostics ranges from document */ getSortedRanges(uri, severity) { let collections = this.getCollections(uri); let res = []; let level = severity ? util_2.severityLevel(severity) : 0; for (let collection of collections) { let diagnostics = collection.get(uri); if (level) diagnostics = diagnostics.filter(o => o.severity == level); let ranges = diagnostics.map(o => o.range); res.push(...ranges); } res.sort((a, b) => { if (a.start.line != b.start.line) { return a.start.line - b.start.line; } return a.start.character - b.start.character; }); return res; } /** * Get readonly diagnostics for a buffer */ getDiagnostics(uri) { let collections = this.getCollections(uri); let { level } = this.config; let res = []; for (let collection of collections) { let items = collection.get(uri); if (!items) continue; if (level && level < vscode_languageserver_protocol_1.DiagnosticSeverity.Hint) { items = items.filter(s => s.severity == null || s.severity <= level); } res.push(...items); } res.sort((a, b) => { if (a.severity == b.severity) { let d = position_1.comparePosition(a.range.start, b.range.start); if (d != 0) return d; if (a.source == b.source) return a.message > b.message ? 1 : -1; return a.source > b.source ? 1 : -1; } return a.severity - b.severity; }); return res; } getDiagnosticsInRange(document, range) { let collections = this.getCollections(document.uri); let res = []; for (let collection of collections) { let items = collection.get(document.uri); if (!items) continue; for (let item of items) { if (position_1.rangeIntersect(item.range, range)) { res.push(item); } } } return res; } /** * Jump to previouse diagnostic position */ async jumpPrevious(severity) { let buffer = await this.nvim.buffer; let document = workspace_1.default.getDocument(buffer.id); if (!document) return; let offset = await workspace_1.default.getOffset(); if (offset == null) return; let ranges = this.getSortedRanges(document.uri, severity); if (ranges.length == 0) { workspace_1.default.showMessage('Empty diagnostics', 'warning'); return; } let { textDocument } = document; for (let i = ranges.length - 1; i >= 0; i--) { if (textDocument.offsetAt(ranges[i].end) < offset) { await this.jumpTo(ranges[i]); return; } } await this.jumpTo(ranges[ranges.length - 1]); } /** * Jump to next diagnostic position */ async jumpNext(severity) { let buffer = await this.nvim.buffer; let document = workspace_1.default.getDocument(buffer.id); let offset = await workspace_1.default.getOffset(); let ranges = this.getSortedRanges(document.uri, severity); if (ranges.length == 0) { workspace_1.default.showMessage('Empty diagnostics', 'warning'); return; } let { textDocument } = document; for (let i = 0; i <= ranges.length - 1; i++) { if (textDocument.offsetAt(ranges[i].start) > offset) { await this.jumpTo(ranges[i]); return; } } await this.jumpTo(ranges[0]); } /** * All diagnostics of current workspace */ getDiagnosticList() { let res = []; for (let collection of this.collections) { collection.forEach((uri, diagnostics) => { let file = vscode_uri_1.URI.parse(uri).fsPath; for (let diagnostic of diagnostics) { let { start } = diagnostic.range; let o = { file, lnum: start.line + 1, col: start.character + 1, message: `[${diagnostic.source || collection.name}${diagnostic.code ? ' ' + diagnostic.code : ''}] ${diagnostic.message}`, severity: util_2.getSeverityName(diagnostic.severity), level: diagnostic.severity || 0, location: vscode_languageserver_protocol_1.Location.create(uri, diagnostic.range) }; res.push(o); } }); } res.sort((a, b) => { if (a.level !== b.level) { return a.level - b.level; } if (a.file !== b.file) { return a.file > b.file ? 1 : -1; } else { if (a.lnum != b.lnum) { return a.lnum - b.lnum; } return a.col - b.col; } }); return res; } async getCurrentDiagnostics() { let [bufnr, cursor] = await this.nvim.eval('[bufnr("%"),coc#util#cursor()]'); let pos = vscode_languageserver_protocol_1.Position.create(cursor[0], cursor[1]); let buffer = this.buffers.find(o => o.bufnr == bufnr); if (!buffer) return []; let { checkCurrentLine } = this.config; let diagnostics = buffer.diagnostics.filter(o => { if (checkCurrentLine) return position_1.lineInRange(pos.line, o.range); return position_1.positionInRange(pos, o.range) == 0; }); return diagnostics; } /** * Echo diagnostic message of currrent position */ async echoMessage(truncate = false) { const config = this.config; if (!this.enabled || config.enableMessage == 'never') return; if (this.timer) clearTimeout(this.timer); let useFloat = config.messageTarget == 'float'; let diagnostics = await this.getCurrentDiagnostics(); if (diagnostics.length == 0) { if (useFloat) { this.floatFactory.close(); } else { let echoLine = await this.nvim.call('coc#util#echo_line'); if (this.lastMessage && this.lastMessage == echoLine.trim()) { this.nvim.command('echo ""', true); } this.lastMessage = ''; } return; } if (truncate && workspace_1.default.insertMode) return; let lines = []; let docs = []; const buf_ft = (await workspace_1.default.document).filetype; const default_ft = config.filetypeMap['default']; const ft = config.filetypeMap.hasOwnProperty(buf_ft) ? config.filetypeMap[buf_ft] : (default_ft === 'bufferType' ? buf_ft : (default_ft ? default_ft : '')); diagnostics.forEach(diagnostic => { let { source, code, severity, message } = diagnostic; let s = util_2.getSeverityName(severity)[0]; let str = `[${source}${code ? ' ' + code : ''}] [${s}] ${message}`; let filetype = 'Error'; if (ft === '') { switch (diagnostic.severity) { case vscode_languageserver_protocol_1.DiagnosticSeverity.Hint: filetype = 'Hint'; break; case vscode_languageserver_protocol_1.DiagnosticSeverity.Warning: filetype = 'Warning'; break; case vscode_languageserver_protocol_1.DiagnosticSeverity.Information: filetype = 'Info'; break; } } else { filetype = ft; } docs.push({ filetype, content: str }); lines.push(...str.split('\n')); }); if (useFloat) { await this.floatFactory.create(docs); } else { this.lastMessage = lines[0]; await this.nvim.command('echo ""'); await workspace_1.default.echoLines(lines, truncate); } } async jumpRelated() { let diagnostics = await this.getCurrentDiagnostics(); if (!diagnostics) return; let diagnostic = diagnostics.find(o => o.relatedInformation != null); if (!diagnostic) return; let locations = diagnostic.relatedInformation.map(o => o.location); if (locations.length == 1) { await workspace_1.default.jumpTo(locations[0].uri, locations[0].range.start); } else if (locations.length > 1) { await workspace_1.default.showLocations(locations); } } hideFloat() { if (this.floatFactory) { this.floatFactory.close(); } } dispose() { for (let collection of this.collections) { collection.dispose(); } if (this.floatFactory) { this.floatFactory.dispose(); } this.buffers.splice(0, this.buffers.length); this.collections = []; util_1.disposeAll(this.disposables); } get nvim() { return workspace_1.default.nvim; } setConfiguration(event) { if (event && !event.affectsConfiguration('diagnostic')) return; let preferences = workspace_1.default.getConfiguration('coc.preferences.diagnostic'); let config = workspace_1.default.getConfiguration('diagnostic'); function getConfig(key, defaultValue) { return preferences.get(key, config.get(key, defaultValue)); } let messageTarget = getConfig('messageTarget', 'float'); if (messageTarget == 'float' && !workspace_1.default.env.floating && !workspace_1.default.env.textprop) { messageTarget = 'echo'; } this.config = { messageTarget, srcId: workspace_1.default.createNameSpace('coc-diagnostic') || 1000, virtualTextSrcId: workspace_1.default.createNameSpace('diagnostic-virtualText'), checkCurrentLine: getConfig('checkCurrentLine', false), enableSign: getConfig('enableSign', true), maxWindowHeight: getConfig('maxWindowHeight', 10), enableMessage: getConfig('enableMessage', 'always'), joinMessageLines: getConfig('joinMessageLines', false), virtualText: getConfig('virtualText', false), virtualTextPrefix: getConfig('virtualTextPrefix', " "), virtualTextLineSeparator: getConfig('virtualTextLineSeparator', " \\ "), virtualTextLines: getConfig('virtualTextLines', 3), displayByAle: getConfig('displayByAle', false), level: util_2.severityLevel(getConfig('level', 'hint')), locationlist: getConfig('locationlist', true), signOffset: getConfig('signOffset', 1000), errorSign: getConfig('errorSign', '>>'), warningSign: getConfig('warningSign', '>>'), infoSign: getConfig('infoSign', '>>'), hintSign: getConfig('hintSign', '>>'), refreshAfterSave: getConfig('refreshAfterSave', false), refreshOnInsertMode: getConfig('refreshOnInsertMode', false), filetypeMap: getConfig('filetypeMap', {}), }; this.enabled = getConfig('enable', true); if (this.config.displayByAle) { this.enabled = false; } if (event) { for (let severity of ['error', 'info', 'warning', 'hint']) { let key = `diagnostic.${severity}Sign`; if (event.affectsConfiguration(key)) { let text = config.get(`${severity}Sign`, '>>'); let name = severity[0].toUpperCase() + severity.slice(1); this.nvim.command(`sign define Coc${name} text=${text} linehl=Coc${name}Line texthl=Coc${name}Sign`, true); } } } } getCollections(uri) { return this.collections.filter(c => c.has(uri)); } shouldValidate(doc) { return doc != null && doc.buftype == ''; } refreshBuffer(uri) { // vim has issue with diagnostic update if (workspace_1.default.insertMode && !this.config.refreshOnInsertMode) return; let buf = this.buffers.find(buf => buf.uri == uri); let { displayByAle } = this.config; if (buf) { if (displayByAle) { let { nvim } = this; let allDiagnostics = new Map(); for (let collection of this.collections) { let diagnostics = collection.get(uri); let aleItems = diagnostics.map(o => { let { range } = o; return { text: o.message, code: o.code, lnum: range.start.line + 1, col: range.start.character + 1, end_lnum: range.end.line + 1, end_col: range.end.character, type: util_2.getSeverityType(o.severity) }; }); let exists = allDiagnostics.get(collection.name); if (exists) { exists.push(...aleItems); } else { allDiagnostics.set(collection.name, aleItems); } } nvim.pauseNotification(); for (let key of allDiagnostics.keys()) { this.nvim.call('ale#other_source#ShowResults', [buf.bufnr, key, allDiagnostics.get(key)], true); } nvim.resumeNotification(false, true).catch(_e => { // noop }); } else { let diagnostics = this.getDiagnostics(uri); if (this.enabled) { buf.refresh(diagnostics); return true; } } } return false; } async jumpTo(range) { if (!range) return; let { start } = range; await this.nvim.call('cursor', [start.line + 1, start.character + 1]); await this.echoMessage(); } } exports.DiagnosticManager = DiagnosticManager; exports.default = new DiagnosticManager(); //# sourceMappingURL=manager.js.map