UNPKG

@jupyter-lsp/jupyterlab-lsp

Version:

Language Server Protocol integration for JupyterLab

237 lines 10.1 kB
import { VDomModel, VDomRenderer } from '@jupyterlab/apputils'; import { caretDownIcon, caretUpIcon } from '@jupyterlab/ui-components'; import React from 'react'; import { DocumentLocator } from '../../components/utils'; import { PositionConverter } from '../../converter'; import { DiagnosticSeverity } from '../../lsp'; import '../../../style/diagnostics_listing.css'; export const DIAGNOSTICS_LISTING_CLASS = 'lsp-diagnostics-listing'; const DIAGNOSTICS_PLACEHOLDER_CLASS = 'lsp-diagnostics-placeholder'; export class DiagnosticsDatabase extends Map { get all() { return [].concat.apply([], this.values()); } } class Column { constructor(options) { this.options = options; this.isVisible = true; } renderCell(data, context) { return this.options.renderCell(data, context); } sort(a, b) { return this.options.sort(a, b); } get id() { return this.options.id; } isAvailable(context) { if (this.options.isAvailable != null) { return this.options.isAvailable(context); } return true; } renderHeader(listing) { return (React.createElement(SortableTH, { label: this.options.label, id: this.id, listing: listing, key: this.id })); } } function SortableTH(props) { const isSortKey = props.id === props.listing.sortKey; const sortIcon = !isSortKey || props.listing.sortDirection === 1 ? caretUpIcon : caretDownIcon; return (React.createElement("th", { key: props.id, onClick: () => props.listing.sort(props.id), className: isSortKey ? 'lsp-sorted-header' : undefined, "data-id": props.id }, React.createElement("div", null, React.createElement("label", null, props.label), React.createElement(sortIcon.react, { tag: "span", className: "lsp-sort-icon" })))); } export function messageWithoutCode(diagnostic) { let message = diagnostic.message; let codeString = '' + diagnostic.code; if (diagnostic.code != null && diagnostic.code !== '' && message.startsWith(codeString + '')) { return message.slice(codeString.length).trim(); } return message; } export class DiagnosticsListing extends VDomRenderer { constructor(model) { super(model); this.sortKey = 'Severity'; this.sortDirection = 1; const trans = model.trans; this.trans = trans; this.severityTranslations = { Error: trans.__('Error'), Warning: trans.__('Warning'), Information: trans.__('Information'), Hint: trans.__('Hint') }; this.columns = [ new Column({ id: 'Virtual Document', label: this.trans.__('Virtual Document'), renderCell: (row, context) => (React.createElement("td", { key: 0 }, React.createElement(DocumentLocator, { document: row.document, adapter: context.adapter, trans: this.trans }))), sort: (a, b) => a.document.idPath.localeCompare(b.document.idPath), isAvailable: context => context.db.size > 1 }), new Column({ id: 'Message', label: this.trans.__('Message'), renderCell: row => { let message = messageWithoutCode(row.data.diagnostic); return React.createElement("td", { key: 1 }, message); }, sort: (a, b) => a.data.diagnostic.message.localeCompare(b.data.diagnostic.message) }), new Column({ id: 'Code', label: this.trans.__('Code'), renderCell: row => React.createElement("td", { key: 2 }, row.data.diagnostic.code), sort: (a, b) => (a.data.diagnostic.code + '').localeCompare(b.data.diagnostic.source + '') }), new Column({ id: 'Severity', label: this.trans.__('Severity'), // TODO: use default diagnostic severity renderCell: row => { const severity = DiagnosticSeverity[row.data.diagnostic.severity || 1]; return (React.createElement("td", { key: 3 }, this.severityTranslations[severity] || severity)); }, sort: (a, b) => { if (!a.data.diagnostic.severity) { return +1; } if (!b.data.diagnostic.severity) { return -1; } return a.data.diagnostic.severity > b.data.diagnostic.severity ? 1 : -1; } }), new Column({ id: 'Source', label: this.trans.__('Source'), renderCell: row => React.createElement("td", { key: 4 }, row.data.diagnostic.source), sort: (a, b) => { if (!a.data.diagnostic.source) { return +1; } if (!b.data.diagnostic.source) { return -1; } return a.data.diagnostic.source.localeCompare(b.data.diagnostic.source); } }), new Column({ id: 'Cell', label: this.trans.__('Cell'), renderCell: row => React.createElement("td", { key: 5 }, row.cellNumber), sort: (a, b) => a.cellNumber - b.cellNumber || a.data.range.start.line - b.data.range.start.line || a.data.range.start.ch - b.data.range.start.ch, isAvailable: context => context.adapter.hasMultipleEditors }), new Column({ id: 'Line:Ch', label: this.trans.__('Line:Ch'), renderCell: row => (React.createElement("td", { key: 6 }, row.data.range.start.line, ":", row.data.range.start.ch)), sort: (a, b) => a.data.range.start.line - b.data.range.start.line || a.data.range.start.ch - b.data.range.start.ch }) ]; } sort(key) { if (key === this.sortKey) { this.sortDirection = this.sortDirection * -1; } else { this.sortKey = key; this.sortDirection = 1; } this.update(); } render() { let diagnosticsDatabase = this.model.diagnostics; const adapter = this.model.adapter; if (diagnosticsDatabase == null || !adapter) { return (React.createElement("div", { className: DIAGNOSTICS_PLACEHOLDER_CLASS }, React.createElement("h3", null, "No diagnostics"), this.trans.__('Diagnostics panel shows linting results in notebooks and files connected to a language server.'))); } if (diagnosticsDatabase.size === 0) { return (React.createElement("div", { className: DIAGNOSTICS_PLACEHOLDER_CLASS }, this.trans.__('No issues detected, great job!'))); } let byDocument = Array.from(diagnosticsDatabase).map(([virtualDocument, diagnostics]) => { if (virtualDocument.isDisposed) { return []; } return diagnostics.map((diagnosticData, i) => { let cellNumber = null; if (adapter.hasMultipleEditors) { const cellIndex = adapter.editors.findIndex(value => value.ceEditor == diagnosticData.editorAccessor); cellNumber = cellIndex + 1; } return { data: diagnosticData, key: virtualDocument.uri + ',' + i, document: virtualDocument, cellNumber: cellNumber }; }); }); let flattened = [].concat.apply([], byDocument); this._diagnostics = new Map(flattened.map(row => [row.key, row])); let sortedColumn = this.columns.filter(column => column.id === this.sortKey)[0]; let sorter = sortedColumn.sort.bind(sortedColumn); let sorted = flattened.sort((a, b) => sorter(a, b) * this.sortDirection); let context = { db: diagnosticsDatabase, adapter: adapter }; let columnsToDisplay = this.columns.filter(column => column.isAvailable(context) && column.isVisible); let elements = sorted.map(row => { let cells = columnsToDisplay.map(column => column.renderCell(row, context)); return (React.createElement("tr", { key: row.key, "data-key": row.key, onClick: () => { return this.jumpTo(row); } }, cells)); }); let columnsHeaders = columnsToDisplay.map(column => column.renderHeader(this)); return (React.createElement("table", { className: DIAGNOSTICS_LISTING_CLASS }, React.createElement("thead", null, React.createElement("tr", null, columnsHeaders)), React.createElement("tbody", null, elements))); } getDiagnostic(key) { if (!this._diagnostics.has(key)) { console.warn('Could not find the diagnostics row with key', key); return; } return this._diagnostics.get(key); } async jumpTo(row) { const editor = await row.data.editorAccessor.reveal(); editor.setCursorPosition(PositionConverter.cm_to_ce(row.data.range.start)); editor.focus(); } } (function (DiagnosticsListing) { /** * A VDomModel for the LSP of current file editor/notebook. */ class Model extends VDomModel { constructor(translatorBundle) { super(); this.trans = translatorBundle; } } DiagnosticsListing.Model = Model; })(DiagnosticsListing || (DiagnosticsListing = {})); //# sourceMappingURL=listing.js.map