UNPKG

@krassowski/jupyterlab-lsp

Version:

Language Server Protocol integration for JupyterLab

166 lines 7.39 kB
import { InputDialog } from '@jupyterlab/apputils'; import { ISettingRegistry } from '@jupyterlab/settingregistry'; import { ITranslator, nullTranslator } from '@jupyterlab/translation'; import { LabIcon } from '@jupyterlab/ui-components'; import renameSvg from '../../style/icons/rename.svg'; import { CodeMirrorIntegration } from '../editor_integration/codemirror'; import { FeatureSettings } from '../feature'; import { ILSPFeatureManager, PLUGIN_ID } from '../tokens'; import { FEATURE_ID as DIAGNOSTICS_PLUGIN_ID } from './diagnostics'; export const renameIcon = new LabIcon({ name: 'lsp:rename', svgstr: renameSvg }); const FEATURE_ID = PLUGIN_ID + ':rename'; const COMMANDS = (trans) => [ { id: 'rename-symbol', execute: async ({ editor, connection, virtual_position, document, features }) => { const rename_feature = features.get(FEATURE_ID); rename_feature.setTrans(trans); let root_position = rename_feature.transform_virtual_position_to_root_position(virtual_position); let old_value = editor.get_token_at(root_position).value; let handle_failure = (error) => { let status = ''; if (features.has(DIAGNOSTICS_PLUGIN_ID)) { let diagnostics_feature = features.get(DIAGNOSTICS_PLUGIN_ID); status = RenameCM.ux_workaround_for_rope_limitation(error, diagnostics_feature, editor, rename_feature); } if (!status) { status = trans.__(`Rename failed: %1`, error); } rename_feature.setStatus(status, 7.5 * 1000); }; const dialog_value = await InputDialog.getText({ title: trans.__('Rename to'), text: old_value, okLabel: trans.__('Rename'), cancelLabel: trans.__('Cancel') }); try { const new_value = dialog_value.value; if (dialog_value.button.accept != true || new_value == null) { // the user has cancelled the rename action or did not provide new value return; } rename_feature.setStatus(trans.__('Renaming %1 to %2...', old_value, new_value), 2 * 1000); const edit = await (connection === null || connection === void 0 ? void 0 : connection.rename(virtual_position, document.document_info, new_value, false)); if (edit) { await rename_feature.handleRename(edit, old_value, new_value); } else { handle_failure(new Error('no edit from server')); } } catch (error) { handle_failure(error); } }, is_enabled: ({ connection }) => connection ? connection.provides('renameProvider') : false, label: trans.__('Rename symbol'), icon: renameIcon } ]; export class RenameCM extends CodeMirrorIntegration { setTrans(trans) { this.trans = trans; } setStatus(message, timeout) { return this.status_message.set(message, timeout); } async handleRename(workspaceEdit, old_value, new_value) { let outcome; try { outcome = await this.apply_edit(workspaceEdit); } catch (error) { this.status_message.set(this.trans.__('Rename failed: %1', error)); return; } try { let status; const change_text = this.trans.__('%1 to %2', old_value, new_value); if (outcome.appliedChanges === 0) { status = this.trans.__('Could not rename %1 - consult the language server documentation', change_text); } else if (outcome.wasGranular) { status = this.trans._n('Renamed %2 in %3 place', 'Renamed %2 in %3 places', outcome.appliedChanges, change_text, outcome.appliedChanges); } else if (this.adapter.has_multiple_editors) { status = this.trans._n('Renamed %2 in %3 cell', 'Renamed %2 in %3 cells', outcome.modifiedCells, change_text, outcome.modifiedCells); } else { status = this.trans.__('Renamed %1', change_text); } if (outcome.errors.length !== 0) { status += this.trans.__(' with errors: %1', outcome.errors); } this.status_message.set(status, 5 * 1000); } catch (error) { this.console.warn(error); } return outcome; } /** * In #115 an issue with rename for Python (when using pyls) was identified: * rename was failing with an obscure message when the source code could * not be parsed correctly by rope (due to a user's syntax error). * * This function detects such a condition using diagnostics feature * and provides a nice error message to the user. */ static ux_workaround_for_rope_limitation(error, diagnostics_feature, editor, rename_feature) { let has_index_error = false; try { has_index_error = error.message.includes('IndexError'); } catch (e) { return null; } if (!has_index_error) { return null; } let dire_python_errors = (diagnostics_feature.diagnostics_db.all || []).filter(diagnostic => diagnostic.diagnostic.message.includes('invalid syntax') || diagnostic.diagnostic.message.includes('SyntaxError') || diagnostic.diagnostic.message.includes('IndentationError')); if (dire_python_errors.length === 0) { return null; } let dire_errors = [ ...new Set(dire_python_errors.map(diagnostic => { let message = diagnostic.diagnostic.message; let start = diagnostic.range.start; if (rename_feature.adapter.has_multiple_editors) { let { index: editor_id } = editor.find_editor(diagnostic.editor); let cell_number = editor_id + 1; // TODO: should we show "code cell" numbers, or just cell number? return rename_feature.trans.__('%1 in cell %2 at line %3', message, cell_number, start.line); } else { return rename_feature.trans.__('%1 at line %2', message, start.line); } })) ].join(', '); return rename_feature.trans.__('Syntax error(s) prevent rename: %1', dire_errors); } } export const RENAME_PLUGIN = { id: FEATURE_ID, requires: [ILSPFeatureManager, ISettingRegistry, ITranslator], autoStart: true, activate: (app, featureManager, settingRegistry, translator) => { const trans = (translator || nullTranslator).load('jupyterlab_lsp'); const settings = new FeatureSettings(settingRegistry, FEATURE_ID); featureManager.register({ feature: { editorIntegrationFactory: new Map([['CodeMirrorEditor', RenameCM]]), id: FEATURE_ID, name: 'LSP Rename', settings: settings, commands: COMMANDS(trans) } }); } }; //# sourceMappingURL=rename.js.map