@krassowski/jupyterlab-lsp
Version:
Language Server Protocol integration for JupyterLab
238 lines • 10 kB
JavaScript
import { VDomModel, VDomRenderer } from '@jupyterlab/apputils';
import { caretDownIcon, caretUpIcon } from '@jupyterlab/ui-components';
import React from 'react';
import { DocumentLocator, focus_on } from '../../components/utils';
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.is_visible = true;
}
render_cell(data, context) {
return this.options.render_cell(data, context);
}
sort(a, b) {
return this.options.sort(a, b);
}
get id() {
return this.options.id;
}
is_available(context) {
if (this.options.is_available != null) {
return this.options.is_available(context);
}
return true;
}
render_header(listing) {
return (React.createElement(SortableTH, { label: this.options.label, id: this.id, listing: listing, key: this.id }));
}
}
function SortableTH(props) {
const is_sort_key = props.id === props.listing.sort_key;
const sortIcon = !is_sort_key || props.listing.sort_direction === 1
? caretUpIcon
: caretDownIcon;
return (React.createElement("th", { key: props.id, onClick: () => props.listing.sort(props.id), className: is_sort_key ? '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 message_without_code(diagnostic) {
let message = diagnostic.message;
let code_str = '' + diagnostic.code;
if (diagnostic.code != null &&
diagnostic.code !== '' &&
message.startsWith(code_str + '')) {
return message.slice(code_str.length).trim();
}
return message;
}
export class DiagnosticsListing extends VDomRenderer {
constructor(model) {
super(model);
this.sort_key = 'Severity';
this.sort_direction = 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'),
render_cell: (row, context) => (React.createElement("td", { key: 0 },
React.createElement(DocumentLocator, { document: row.document, adapter: context.adapter, trans: this.trans }))),
sort: (a, b) => a.document.id_path.localeCompare(b.document.id_path),
is_available: context => context.db.size > 1
}),
new Column({
id: 'Message',
label: this.trans.__('Message'),
render_cell: row => {
let message = message_without_code(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'),
render_cell: 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
render_cell: 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'),
render_cell: 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'),
render_cell: row => React.createElement("td", { key: 5 }, row.cell_number),
sort: (a, b) => a.cell_number - b.cell_number ||
a.data.range.start.line - b.data.range.start.line ||
a.data.range.start.ch - b.data.range.start.ch,
is_available: context => context.adapter.has_multiple_editors
}),
new Column({
id: 'Line:Ch',
label: this.trans.__('Line:Ch'),
render_cell: 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.sort_key) {
this.sort_direction = this.sort_direction * -1;
}
else {
this.sort_key = key;
this.sort_direction = 1;
}
this.update();
}
render() {
let diagnostics_db = this.model.diagnostics;
const editor = this.model.virtual_editor;
const adapter = this.model.adapter;
if (diagnostics_db == null || editor == null || !adapter) {
return (React.createElement("div", { className: DIAGNOSTICS_PLACEHOLDER_CLASS }, this.trans.__('Diagnostics are not available')));
}
if (diagnostics_db.size === 0) {
return (React.createElement("div", { className: DIAGNOSTICS_PLACEHOLDER_CLASS }, this.trans.__('No issues detected, great job!')));
}
let by_document = Array.from(diagnostics_db).map(([virtual_document, diagnostics]) => {
if (virtual_document.isDisposed) {
return [];
}
return diagnostics.map((diagnostic_data, i) => {
let cell_number = null;
if (adapter.has_multiple_editors) {
let { index: cell_id } = editor.find_editor(diagnostic_data.editor);
cell_number = cell_id + 1;
}
return {
data: diagnostic_data,
key: virtual_document.uri + ',' + i,
document: virtual_document,
cell_number: cell_number,
editor: editor
};
});
});
let flattened = [].concat.apply([], by_document);
this._diagnostics = new Map(flattened.map(row => [row.key, row]));
let sorted_column = this.columns.filter(column => column.id === this.sort_key)[0];
let sorter = sorted_column.sort.bind(sorted_column);
let sorted = flattened.sort((a, b) => sorter(a, b) * this.sort_direction);
let context = {
db: diagnostics_db,
editor: editor,
adapter: adapter
};
let columns_to_display = this.columns.filter(column => column.is_available(context) && column.is_visible);
let elements = sorted.map(row => {
let cells = columns_to_display.map(column => column.render_cell(row, context));
return (React.createElement("tr", { key: row.key, "data-key": row.key, onClick: () => {
this.jump_to(row);
} }, cells));
});
let columns_headers = columns_to_display.map(column => column.render_header(this));
return (React.createElement("table", { className: DIAGNOSTICS_LISTING_CLASS },
React.createElement("thead", null,
React.createElement("tr", null, columns_headers)),
React.createElement("tbody", null, elements)));
}
get_diagnostic(key) {
if (!this._diagnostics.has(key)) {
console.warn('Could not find the diagnostics row with key', key);
return;
}
return this._diagnostics.get(key);
}
jump_to(row) {
const cm_editor = row.data.editor;
focus_on(cm_editor.getWrapperElement());
cm_editor.getDoc().setCursor(row.data.range.start);
cm_editor.focus();
}
}
(function (DiagnosticsListing) {
/**
* A VDomModel for the LSP of current file editor/notebook.
*/
class Model extends VDomModel {
constructor(translator_bundle) {
super();
this.trans = translator_bundle;
}
}
DiagnosticsListing.Model = Model;
})(DiagnosticsListing || (DiagnosticsListing = {}));
//# sourceMappingURL=listing.js.map