|
@@ -0,0 +1,510 @@
|
|
|
+import { IEditorTracker } from '@jupyterlab/fileeditor';
|
|
|
+import { INotebookTracker } from '@jupyterlab/notebook';
|
|
|
+import { LabIcon } from '@jupyterlab/ui-components';
|
|
|
+import { ICommandPalette, InputDialog, ReactWidget } from '@jupyterlab/apputils';
|
|
|
+import { Menu } from '@lumino/widgets';
|
|
|
+import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
|
+import { IStatusBar, TextItem } from '@jupyterlab/statusbar';
|
|
|
+import { ICodeMirror } from '@jupyterlab/codemirror';
|
|
|
+import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
|
+import { requestAPI } from './handler';
|
|
|
+import '../style/index.css';
|
|
|
+import spellcheckSvg from '../style/icons/ic-baseline-spellcheck.svg';
|
|
|
+export const spellcheckIcon = new LabIcon({
|
|
|
+ name: 'spellcheck:spellcheck',
|
|
|
+ svgstr: spellcheckSvg
|
|
|
+});
|
|
|
+// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
|
+const Typo = require('typo-js');
|
|
|
+class LanguageManager {
|
|
|
+ /**
|
|
|
+ * initialise the manager
|
|
|
+ * mainly reading the definitions from the external extension
|
|
|
+ */
|
|
|
+ constructor(settingsRegistry) {
|
|
|
+ const loadSettings = settingsRegistry.load(extension.id).then(settings => {
|
|
|
+ this.updateSettings(settings);
|
|
|
+ settings.changed.connect(() => {
|
|
|
+ this.updateSettings(settings);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ this.ready = Promise.all([
|
|
|
+ this.fetchServerDictionariesList(),
|
|
|
+ loadSettings
|
|
|
+ ]).then(() => {
|
|
|
+ console.debug('LanguageManager is ready');
|
|
|
+ });
|
|
|
+ }
|
|
|
+ updateSettings(settings) {
|
|
|
+ if (settings) {
|
|
|
+ this.onlineDictionaries = settings.get('onlineDictionaries').composite.map(dictionary => {
|
|
|
+ return Object.assign(Object.assign({}, dictionary), { isOnline: true });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Read the list of languages from the server extension
|
|
|
+ */
|
|
|
+ fetchServerDictionariesList() {
|
|
|
+ return requestAPI('language_manager').then(values => {
|
|
|
+ console.debug('Dictionaries fetched from server');
|
|
|
+ this.serverDictionaries = values.dictionaries.map(dictionary => {
|
|
|
+ return Object.assign(Object.assign({}, dictionary), { isOnline: false });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ get dictionaries() {
|
|
|
+ return [...this.serverDictionaries, ...this.onlineDictionaries];
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * get an array of languages, put "language" in front of the list
|
|
|
+ * the list is alphabetically sorted
|
|
|
+ */
|
|
|
+ getChoices(language) {
|
|
|
+ const options = language
|
|
|
+ ? [language, ...this.dictionaries.filter(l => l.id !== language.id)]
|
|
|
+ : this.dictionaries;
|
|
|
+ return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * select the language by the identifier
|
|
|
+ */
|
|
|
+ getLanguageByIdentifier(identifier) {
|
|
|
+ const exactMatch = this.dictionaries.find(l => l.id === identifier);
|
|
|
+ if (exactMatch) {
|
|
|
+ return exactMatch;
|
|
|
+ }
|
|
|
+ // approximate matches support transition from the 0.5 version (and older)
|
|
|
+ // that used incorrect codes as language identifiers
|
|
|
+ const approximateMatch = this.dictionaries.find(l => l.id.toLowerCase() === identifier.replace('-', '_').toLowerCase());
|
|
|
+ if (approximateMatch) {
|
|
|
+ console.warn(`Language identifier ${identifier} has a non-exact match, please update it to ${approximateMatch.id}`);
|
|
|
+ return approximateMatch;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+class StatusWidget extends ReactWidget {
|
|
|
+ constructor(source) {
|
|
|
+ super();
|
|
|
+ this.language_source = source;
|
|
|
+ }
|
|
|
+ render() {
|
|
|
+ return TextItem({ source: this.language_source() });
|
|
|
+ }
|
|
|
+}
|
|
|
+/**
|
|
|
+ * SpellChecker
|
|
|
+ */
|
|
|
+class SpellChecker {
|
|
|
+ constructor(app, tracker, editor_tracker, setting_registry, code_mirror, translator, palette, status_bar) {
|
|
|
+ this.app = app;
|
|
|
+ this.tracker = tracker;
|
|
|
+ this.editor_tracker = editor_tracker;
|
|
|
+ this.setting_registry = setting_registry;
|
|
|
+ this.code_mirror = code_mirror;
|
|
|
+ this.palette = palette;
|
|
|
+ this.status_bar = status_bar;
|
|
|
+ // Default Options
|
|
|
+ this.check_spelling = true;
|
|
|
+ this.rx_word_char = /[^-[\]{}():/!;&@$£%§<>"*+=?.,~\\^|_`#±\s\t]/;
|
|
|
+ this.rx_non_word_char = /[-[\]{}():/!;&@$£%§<>"*+=?.,~\\^|_`#±\s\t]/;
|
|
|
+ this.ignored_tokens = new Set();
|
|
|
+ this.define_mode = (original_mode_spec) => {
|
|
|
+ if (original_mode_spec.indexOf('spellcheck_') === 0) {
|
|
|
+ return original_mode_spec;
|
|
|
+ }
|
|
|
+ const new_mode_spec = 'spellcheck_' + original_mode_spec;
|
|
|
+ this.code_mirror.CodeMirror.defineMode(new_mode_spec, (config) => {
|
|
|
+ const spellchecker_overlay = {
|
|
|
+ name: new_mode_spec,
|
|
|
+ token: (stream, state) => {
|
|
|
+ if (stream.eatWhile(this.rx_word_char)) {
|
|
|
+ const word = stream.current().replace(/(^')|('$)/g, '');
|
|
|
+ if (word !== '' &&
|
|
|
+ !word.match(/^\d+$/) &&
|
|
|
+ this.dictionary !== undefined &&
|
|
|
+ !this.dictionary.check(word) &&
|
|
|
+ !this.ignored_tokens.has(word)) {
|
|
|
+ return 'spell-error';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ stream.eatWhile(this.rx_non_word_char);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ return this.code_mirror.CodeMirror.overlayMode(this.code_mirror.CodeMirror.getMode(config, original_mode_spec), spellchecker_overlay, true);
|
|
|
+ });
|
|
|
+ return new_mode_spec;
|
|
|
+ };
|
|
|
+ // use the language_manager
|
|
|
+ this.language_manager = new LanguageManager(setting_registry);
|
|
|
+ this._trans = translator.load('jupyterlab-spellchecker');
|
|
|
+ this.status_msg = this._trans.__('Dictionary not loaded');
|
|
|
+ this.TEXT_SUGGESTIONS_AVAILABLE = this._trans.__('Adjust spelling to');
|
|
|
+ this.TEXT_NO_SUGGESTIONS = this._trans.__('No spellcheck suggestions');
|
|
|
+ this.PALETTE_CATEGORY = this._trans.__('Spell Checker');
|
|
|
+ // read the settings
|
|
|
+ this.setup_settings();
|
|
|
+ // setup the static content of the spellchecker UI
|
|
|
+ this.setup_button();
|
|
|
+ this.setup_suggestions();
|
|
|
+ this.setup_language_picker();
|
|
|
+ this.setup_ignore_action();
|
|
|
+ this.tracker.activeCellChanged.connect(() => {
|
|
|
+ if (this.tracker.activeCell) {
|
|
|
+ this.setup_cell_editor(this.tracker.activeCell);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // setup newly open editors
|
|
|
+ this.editor_tracker.widgetAdded.connect((sender, widget) => this.setup_file_editor(widget.content, true));
|
|
|
+ // refresh already open editors when activated (because the MIME type might have changed)
|
|
|
+ this.editor_tracker.currentChanged.connect((sender, widget) => {
|
|
|
+ if (widget !== null) {
|
|
|
+ this.setup_file_editor(widget.content, false);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ // move the load_dictionary into the setup routine, because then
|
|
|
+ // we know that the values are set correctly!
|
|
|
+ setup_settings() {
|
|
|
+ Promise.all([
|
|
|
+ this.setting_registry.load(extension.id),
|
|
|
+ this.app.restored,
|
|
|
+ this.language_manager.ready
|
|
|
+ ])
|
|
|
+ .then(([settings]) => {
|
|
|
+ this.update_settings(settings);
|
|
|
+ settings.changed.connect(() => {
|
|
|
+ this.update_settings(settings);
|
|
|
+ });
|
|
|
+ })
|
|
|
+ .catch((reason) => {
|
|
|
+ console.error(reason.message);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ _set_theme(name) {
|
|
|
+ document.body.setAttribute('data-jp-spellchecker-theme', name);
|
|
|
+ }
|
|
|
+ update_settings(settings) {
|
|
|
+ this.settings = settings;
|
|
|
+ const tokens = settings.get('ignore').composite;
|
|
|
+ this.ignored_tokens = new Set(tokens);
|
|
|
+ this.accepted_types = settings.get('mimeTypes').composite;
|
|
|
+ const theme = settings.get('theme').composite;
|
|
|
+ this._set_theme(theme);
|
|
|
+ // read the saved language setting
|
|
|
+ const language_id = settings.get('language').composite;
|
|
|
+ const user_language = this.language_manager.getLanguageByIdentifier(language_id);
|
|
|
+ if (user_language === undefined) {
|
|
|
+ console.warn('The language ' + language_id + ' is not supported!');
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ this.language = user_language;
|
|
|
+ // load the dictionary
|
|
|
+ this.load_dictionary().catch(console.warn);
|
|
|
+ }
|
|
|
+ this.refresh_state();
|
|
|
+ }
|
|
|
+ setup_file_editor(file_editor, setup_signal = false) {
|
|
|
+ if (this.accepted_types &&
|
|
|
+ this.accepted_types.indexOf(file_editor.model.mimeType) !== -1) {
|
|
|
+ const editor = this.extract_editor(file_editor);
|
|
|
+ this.setup_overlay(editor);
|
|
|
+ }
|
|
|
+ if (setup_signal) {
|
|
|
+ file_editor.model.mimeTypeChanged.connect((model, args) => {
|
|
|
+ // putting at the end of execution queue to allow the CodeMirror mode to be updated
|
|
|
+ setTimeout(() => this.setup_file_editor(file_editor), 0);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setup_cell_editor(cell) {
|
|
|
+ if (cell !== null && cell.model.type === 'markdown') {
|
|
|
+ const editor = this.extract_editor(cell);
|
|
|
+ this.setup_overlay(editor);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ extract_editor(cell_or_editor) {
|
|
|
+ const editor_temp = cell_or_editor.editor;
|
|
|
+ return editor_temp.editor;
|
|
|
+ }
|
|
|
+ setup_overlay(editor, retry = true) {
|
|
|
+ const current_mode = editor.getOption('mode');
|
|
|
+ if (current_mode === 'null') {
|
|
|
+ if (retry) {
|
|
|
+ // putting at the end of execution queue to allow the CodeMirror mode to be updated
|
|
|
+ setTimeout(() => this.setup_overlay(editor, false), 0);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (this.check_spelling) {
|
|
|
+ editor.setOption('mode', this.define_mode(current_mode));
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const original_mode = current_mode.match(/^spellcheck_/)
|
|
|
+ ? current_mode.substr(11)
|
|
|
+ : current_mode;
|
|
|
+ editor.setOption('mode', original_mode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ toggle_spellcheck() {
|
|
|
+ this.check_spelling = !this.check_spelling;
|
|
|
+ console.log('Spell checking is currently: ', this.check_spelling);
|
|
|
+ }
|
|
|
+ setup_button() {
|
|
|
+ this.app.commands.addCommand("spellchecker:toggle-check-spelling" /* toggle */, {
|
|
|
+ label: this._trans.__('Toggle spellchecker'),
|
|
|
+ execute: () => {
|
|
|
+ this.toggle_spellcheck();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (this.palette) {
|
|
|
+ this.palette.addItem({
|
|
|
+ command: "spellchecker:toggle-check-spelling" /* toggle */,
|
|
|
+ category: this.PALETTE_CATEGORY
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ get_contextmenu_context() {
|
|
|
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
+ // @ts-ignore
|
|
|
+ const event = this.app._contextMenuEvent;
|
|
|
+ const target = event.target;
|
|
|
+ const code_mirror_wrapper = target.closest('.CodeMirror');
|
|
|
+ if (code_mirror_wrapper === null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ const code_mirror = code_mirror_wrapper.CodeMirror;
|
|
|
+ const position = code_mirror.coordsChar({
|
|
|
+ left: event.clientX,
|
|
|
+ top: event.clientY
|
|
|
+ });
|
|
|
+ return {
|
|
|
+ editor: code_mirror,
|
|
|
+ position: position
|
|
|
+ };
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * This is different from token as implemented in CodeMirror
|
|
|
+ * and needed because Markdown does not tokenize words
|
|
|
+ * (each letter outside of markdown features is a separate token!)
|
|
|
+ */
|
|
|
+ get_current_word(context) {
|
|
|
+ const { editor, position } = context;
|
|
|
+ const line = editor.getDoc().getLine(position.line);
|
|
|
+ let start = position.ch;
|
|
|
+ while (start > 0 && line[start].match(this.rx_word_char)) {
|
|
|
+ start--;
|
|
|
+ }
|
|
|
+ let end = position.ch;
|
|
|
+ while (end < line.length && line[end].match(this.rx_word_char)) {
|
|
|
+ end++;
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ line: position.line,
|
|
|
+ start: start,
|
|
|
+ end: end,
|
|
|
+ text: line.substring(start, end)
|
|
|
+ };
|
|
|
+ }
|
|
|
+ setup_suggestions() {
|
|
|
+ this.suggestions_menu = new Menu({ commands: this.app.commands });
|
|
|
+ this.suggestions_menu.title.label = this.TEXT_SUGGESTIONS_AVAILABLE;
|
|
|
+ this.suggestions_menu.title.icon = spellcheckIcon.bindprops({
|
|
|
+ stylesheet: 'menuItem'
|
|
|
+ });
|
|
|
+ // this command is not meant to be show - it is just menu trigger detection hack
|
|
|
+ this.app.commands.addCommand("spellchecker:update-suggestions" /* updateSuggestions */, {
|
|
|
+ execute: args => {
|
|
|
+ // no-op
|
|
|
+ },
|
|
|
+ isVisible: args => {
|
|
|
+ this.prepare_suggestions();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.app.contextMenu.addItem({
|
|
|
+ selector: '.cm-spell-error',
|
|
|
+ command: "spellchecker:update-suggestions" /* updateSuggestions */
|
|
|
+ });
|
|
|
+ // end of the menu trigger detection hack
|
|
|
+ this.app.contextMenu.addItem({
|
|
|
+ selector: '.cm-spell-error',
|
|
|
+ submenu: this.suggestions_menu,
|
|
|
+ type: 'submenu'
|
|
|
+ });
|
|
|
+ this.app.commands.addCommand("spellchecker:apply-suggestion" /* applySuggestion */, {
|
|
|
+ execute: args => {
|
|
|
+ this.apply_suggestion(args['name']);
|
|
|
+ },
|
|
|
+ label: args => args['name']
|
|
|
+ });
|
|
|
+ }
|
|
|
+ setup_ignore_action() {
|
|
|
+ this.app.commands.addCommand("spellchecker:ignore" /* ignoreWord */, {
|
|
|
+ execute: () => {
|
|
|
+ this.ignore();
|
|
|
+ },
|
|
|
+ label: this._trans.__('Ignore')
|
|
|
+ });
|
|
|
+ this.app.contextMenu.addItem({
|
|
|
+ selector: '.cm-spell-error',
|
|
|
+ command: "spellchecker:ignore" /* ignoreWord */
|
|
|
+ });
|
|
|
+ }
|
|
|
+ ignore() {
|
|
|
+ const context = this.get_contextmenu_context();
|
|
|
+ if (context === null) {
|
|
|
+ console.log('Could not ignore the word as the context was no longer available');
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const word = this.get_current_word(context);
|
|
|
+ this.settings
|
|
|
+ .set('ignore', [
|
|
|
+ word.text.trim(),
|
|
|
+ ...this.settings.get('ignore').composite
|
|
|
+ ])
|
|
|
+ .catch(console.warn);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ prepare_suggestions() {
|
|
|
+ const context = this.get_contextmenu_context();
|
|
|
+ let suggestions;
|
|
|
+ if (context === null) {
|
|
|
+ // no context (e.g. the edit was applied and the token is no longer in DOM,
|
|
|
+ // so we cannot find the parent editor
|
|
|
+ suggestions = [];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const word = this.get_current_word(context);
|
|
|
+ suggestions = this.dictionary.suggest(word.text);
|
|
|
+ }
|
|
|
+ this.suggestions_menu.clearItems();
|
|
|
+ if (suggestions.length) {
|
|
|
+ for (const suggestion of suggestions) {
|
|
|
+ this.suggestions_menu.addItem({
|
|
|
+ command: "spellchecker:apply-suggestion" /* applySuggestion */,
|
|
|
+ args: { name: suggestion }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ this.suggestions_menu.title.label = this.TEXT_SUGGESTIONS_AVAILABLE;
|
|
|
+ this.suggestions_menu.title.className = '';
|
|
|
+ this.suggestions_menu.setHidden(false);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ this.suggestions_menu.title.className = 'lm-mod-disabled';
|
|
|
+ this.suggestions_menu.title.label = this.TEXT_NO_SUGGESTIONS;
|
|
|
+ this.suggestions_menu.setHidden(true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ apply_suggestion(replacement) {
|
|
|
+ const context = this.get_contextmenu_context();
|
|
|
+ if (context === null) {
|
|
|
+ console.warn('Applying suggestion failed (probably was already applied earlier)');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const word = this.get_current_word(context);
|
|
|
+ context.editor.getDoc().replaceRange(replacement, {
|
|
|
+ ch: word.start,
|
|
|
+ line: word.line
|
|
|
+ }, {
|
|
|
+ ch: word.end,
|
|
|
+ line: word.line
|
|
|
+ });
|
|
|
+ }
|
|
|
+ load_dictionary() {
|
|
|
+ const language = this.language;
|
|
|
+ if (!language) {
|
|
|
+ return new Promise((accept, reject) => reject('Cannot load dictionary: no language set'));
|
|
|
+ }
|
|
|
+ this.status_msg = this._trans.__('Loading dictionary…');
|
|
|
+ this.status_widget.update();
|
|
|
+ return Promise.all([
|
|
|
+ fetch(language.aff).then(res => res.text()),
|
|
|
+ fetch(language.dic).then(res => res.text())
|
|
|
+ ]).then(values => {
|
|
|
+ this.dictionary = new Typo(language.name, values[0], values[1]);
|
|
|
+ console.debug('Dictionary Loaded ', language.name, language.id);
|
|
|
+ this.status_msg = language.name;
|
|
|
+ // update the complete UI
|
|
|
+ this.status_widget.update();
|
|
|
+ this.refresh_state();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ refresh_state() {
|
|
|
+ // update the active cell (if any)
|
|
|
+ if (this.tracker.activeCell !== null) {
|
|
|
+ this.setup_cell_editor(this.tracker.activeCell);
|
|
|
+ }
|
|
|
+ // update the current file editor (if any)
|
|
|
+ if (this.editor_tracker.currentWidget !== null) {
|
|
|
+ this.setup_file_editor(this.editor_tracker.currentWidget.content);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ choose_language() {
|
|
|
+ const choices = this.language_manager.getChoices(this.language);
|
|
|
+ const choiceStrings = choices.map(
|
|
|
+ // note: two dictionaries may exist for a language with the same name,
|
|
|
+ // so we append the actual id of the dictionary in the square brackets.
|
|
|
+ dictionary => dictionary.isOnline
|
|
|
+ ? this._trans.__('%1 [%2] (online)', dictionary.name, dictionary.id)
|
|
|
+ : this._trans.__('%1 [%2]', dictionary.name, dictionary.id));
|
|
|
+ InputDialog.getItem({
|
|
|
+ title: this._trans.__('Choose spellchecker language'),
|
|
|
+ items: choiceStrings
|
|
|
+ }).then(value => {
|
|
|
+ if (value.value !== null) {
|
|
|
+ const index = choiceStrings.indexOf(value.value);
|
|
|
+ const lang = this.language_manager.getLanguageByIdentifier(choices[index].id);
|
|
|
+ if (!lang) {
|
|
|
+ console.error('Language could not be matched - please report this as an issue');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.language = lang;
|
|
|
+ // the setup routine will load the dictionary
|
|
|
+ this.settings.set('language', this.language.id).catch(console.warn);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ setup_language_picker() {
|
|
|
+ this.status_widget = new StatusWidget(() => this.status_msg);
|
|
|
+ this.status_widget.node.onclick = () => {
|
|
|
+ this.choose_language();
|
|
|
+ };
|
|
|
+ this.app.commands.addCommand("spellchecker:choose-language" /* chooseLanguage */, {
|
|
|
+ execute: args => this.choose_language(),
|
|
|
+ label: this._trans.__('Choose spellchecker language')
|
|
|
+ });
|
|
|
+ if (this.palette) {
|
|
|
+ this.palette.addItem({
|
|
|
+ command: "spellchecker:choose-language" /* chooseLanguage */,
|
|
|
+ category: this.PALETTE_CATEGORY
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (this.status_bar) {
|
|
|
+ this.status_bar.registerStatusItem('spellchecker:choose-language', {
|
|
|
+ align: 'right',
|
|
|
+ item: this.status_widget
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+/**
|
|
|
+ * Activate extension
|
|
|
+ */
|
|
|
+function activate(app, tracker, editor_tracker, setting_registry, code_mirror, translator, palette, status_bar) {
|
|
|
+ console.log('Attempting to load spellchecker');
|
|
|
+ const sp = new SpellChecker(app, tracker, editor_tracker, setting_registry, code_mirror, translator || nullTranslator, palette, status_bar);
|
|
|
+ console.log('Spellchecker Loaded ', sp);
|
|
|
+}
|
|
|
+/**
|
|
|
+ * Initialization data for the jupyterlab_spellchecker extension.
|
|
|
+ */
|
|
|
+const extension = {
|
|
|
+ id: '@ijmbarr/jupyterlab_spellchecker:plugin',
|
|
|
+ autoStart: true,
|
|
|
+ requires: [INotebookTracker, IEditorTracker, ISettingRegistry, ICodeMirror],
|
|
|
+ optional: [ITranslator, ICommandPalette, IStatusBar],
|
|
|
+ activate: activate
|
|
|
+};
|
|
|
+export default extension;
|