@jupyter-lsp/jupyterlab-lsp
Version:
Language Server Protocol integration for JupyterLab
233 lines • 9.23 kB
JavaScript
import { MainAreaWidget, Notification } from '@jupyterlab/apputils';
import { nullTranslator } from '@jupyterlab/translation';
import { LabIcon, copyIcon } from '@jupyterlab/ui-components';
import { Menu } from '@lumino/widgets';
import diagnosticsSvg from '../../../style/icons/diagnostics.svg';
import { jumpToIcon } from '../jump_to';
import { DIAGNOSTICS_LISTING_CLASS, DiagnosticsDatabase, DiagnosticsListing } from './listing';
export const diagnosticsIcon = new LabIcon({
name: 'lsp:diagnostics',
svgstr: diagnosticsSvg
});
const CMD_COLUMN_VISIBILITY = 'lsp-set-column-visibility';
const CMD_JUMP_TO_DIAGNOSTIC = 'lsp-jump-to-diagnostic';
const CMD_COPY_DIAGNOSTIC = 'lsp-copy-diagnostic';
const CMD_IGNORE_DIAGNOSTIC_CODE = 'lsp-ignore-diagnostic-code';
const CMD_IGNORE_DIAGNOSTIC_MSG = 'lsp-ignore-diagnostic-message';
/**
* Escape pattern to form a base of a regular expression.
* The snippet comes from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
* and is in the Public Domain (CC0):
* > Any copyright is dedicated to the Public Domain.
* > http://creativecommons.org/publicdomain/zero/1.0/
*/
function escapeRegExp(string) {
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
}
class DiagnosticsPanel {
constructor(trans) {
this._content = null;
this._widget = null;
this.isRegistered = false;
this.trans = trans;
}
get widget() {
if (this._widget == null || this._widget.content.model == null) {
if (this._widget && !this._widget.isDisposed) {
this._widget.dispose();
}
this._widget = this.initWidget();
}
return this._widget;
}
get content() {
return this.widget.content;
}
initWidget() {
this._content = new DiagnosticsListing(new DiagnosticsListing.Model(this.trans));
this._content.model.diagnostics = new DiagnosticsDatabase();
this._content.addClass('lsp-diagnostics-panel-content');
const widget = new MainAreaWidget({ content: this._content });
widget.id = 'lsp-diagnostics-panel';
widget.title.label = this.trans.__('Diagnostics Panel');
widget.title.closable = true;
widget.title.icon = diagnosticsIcon;
return widget;
}
update() {
// if not attached, do not bother to update
if (!this.widget.isAttached) {
return;
}
this.widget.content.update();
}
register(app) {
const widget = this.widget;
let getColumn = (id) => {
// TODO: a hashmap in the panel itself?
for (let column of widget.content.columns) {
if (column.id === id) {
return column;
}
}
return undefined;
};
/** Columns Menu **/
let columnsMenu = new Menu({ commands: app.commands });
columnsMenu.title.label = this.trans.__('Panel columns');
app.commands.addCommand(CMD_COLUMN_VISIBILITY, {
execute: args => {
let column = getColumn(args['id']);
column.isVisible = !column.isVisible;
widget.update();
},
label: args => this.trans.__(args['id']),
isToggled: args => {
let column = getColumn(args['id']);
return column ? column.isVisible : false;
}
});
for (let column of widget.content.columns) {
columnsMenu.addItem({
command: CMD_COLUMN_VISIBILITY,
args: { id: column.id }
});
}
app.contextMenu.addItem({
selector: '.' + DIAGNOSTICS_LISTING_CLASS + ' th',
submenu: columnsMenu,
type: 'submenu'
});
/** Diagnostics Menu **/
let ignoreDiagnosticsMenu = new Menu({ commands: app.commands });
ignoreDiagnosticsMenu.title.label = this.trans.__('Ignore diagnostics like this');
let getRow = () => {
let tr = app.contextMenuHitTest(node => node.tagName.toLowerCase() == 'tr');
if (!tr) {
return;
}
return this.widget.content.getDiagnostic(tr.dataset.key);
};
ignoreDiagnosticsMenu.addItem({
command: CMD_IGNORE_DIAGNOSTIC_CODE
});
ignoreDiagnosticsMenu.addItem({
command: CMD_IGNORE_DIAGNOSTIC_MSG
});
app.commands.addCommand(CMD_IGNORE_DIAGNOSTIC_CODE, {
execute: () => {
const row = getRow();
if (!row) {
console.warn('LPS: diagnostics row not found for ignore code execute()');
return;
}
const diagnostic = row.data.diagnostic;
let current = this.content.model.settings.composite.ignoreCodes;
this.content.model.settings.set('ignoreCodes', [
...current,
diagnostic.code
]);
this.feature.refreshDiagnostics();
},
isVisible: () => {
const row = getRow();
if (!row) {
return false;
}
const diagnostic = row.data.diagnostic;
return !!diagnostic.code;
},
label: () => {
const row = getRow();
if (!row) {
return '';
}
const diagnostic = row.data.diagnostic;
return this.trans.__('Ignore diagnostics with "%1" code', diagnostic.code);
}
});
app.commands.addCommand(CMD_IGNORE_DIAGNOSTIC_MSG, {
execute: () => {
const row = getRow();
if (!row) {
console.warn('LPS: diagnostics row not found for ignore message execute()');
return;
}
const diagnostic = row.data.diagnostic;
let current = this.content.model.settings.composite.ignoreMessagesPatterns;
this.content.model.settings.set('ignoreMessagesPatterns', [
...current,
escapeRegExp(diagnostic.message)
]);
this.feature.refreshDiagnostics();
},
isVisible: () => {
const row = getRow();
if (!row) {
return false;
}
const diagnostic = row.data.diagnostic;
return !!diagnostic.message;
},
label: () => {
const row = getRow();
if (!row) {
return '';
}
const diagnostic = row.data.diagnostic;
return this.trans.__('Ignore diagnostics with "%1" message', diagnostic.message);
}
});
app.commands.addCommand(CMD_JUMP_TO_DIAGNOSTIC, {
execute: () => {
const row = getRow();
if (!row) {
console.warn('LPS: diagnostics row not found for jump execute()');
return;
}
return this.widget.content.jumpTo(row);
},
label: this.trans.__('Jump to location'),
icon: jumpToIcon
});
app.commands.addCommand(CMD_COPY_DIAGNOSTIC, {
execute: () => {
const row = getRow();
if (!row) {
console.warn('LPS: diagnostics row not found for copy execute()');
return;
}
const message = row.data.diagnostic.message;
navigator.clipboard
.writeText(message)
.then(() => {
Notification.info(this.trans.__('Successfully copied "%1" to clipboard', message), {
autoClose: 3 * 1000
});
})
.catch(() => {
console.warn('Could not copy with clipboard.writeText interface, falling back');
window.prompt(this.trans.__('Your browser protects clipboard from write operations; please copy the message manually'), message);
});
},
label: this.trans.__("Copy diagnostics' message"),
icon: copyIcon
});
app.contextMenu.addItem({
selector: '.' + DIAGNOSTICS_LISTING_CLASS + ' tbody tr',
command: CMD_COPY_DIAGNOSTIC
});
app.contextMenu.addItem({
selector: '.' + DIAGNOSTICS_LISTING_CLASS + ' tbody tr',
command: CMD_JUMP_TO_DIAGNOSTIC
});
app.contextMenu.addItem({
selector: '.' + DIAGNOSTICS_LISTING_CLASS + ' tbody tr',
submenu: ignoreDiagnosticsMenu,
type: 'submenu'
});
this.isRegistered = true;
}
}
export const diagnosticsPanel = new DiagnosticsPanel(nullTranslator.load('jupyterlab_lsp'));
//# sourceMappingURL=diagnostics.js.map