diff --git a/packages/base/package.json b/packages/base/package.json index 2ddab6a..96d6bc5 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -34,10 +34,20 @@ "watch": "tsc -w" }, "dependencies": { + "@jupyter/ydoc": "^3.0.0", "@jupyterlab/apputils": "^4.0.0", + "@jupyterlab/cells": "^4.0.0", + "@jupyterlab/codemirror": "^4.0.0", "@jupyterlab/docregistry": "^4.0.0", + "@jupyterlab/nbformat": "^4.0.0", + "@jupyterlab/notebook": "^4.0.0", + "@jupyterlab/rendermime": "^4.0.0", "@jupyterlab/ui-components": "^4.0.0", - "@lumino/widgets": "^2.0.0" + "@lumino/coreutils": "^2.0.0", + "@lumino/disposable": "^2.0.0", + "@lumino/signaling": "^2.0.0", + "@lumino/widgets": "^2.0.0", + "typestyle": "^2.4.0" }, "devDependencies": { "rimraf": "^3.0.2", diff --git a/packages/base/src/index.ts b/packages/base/src/index.ts index 678501a..098fe28 100644 --- a/packages/base/src/index.ts +++ b/packages/base/src/index.ts @@ -1,3 +1,5 @@ export * from './types'; export * from './suggestionsPanel'; export * from './icons'; +export * from './tokens'; +export * from './localSuggestionsManager'; diff --git a/packages/base/src/localSuggestionsManager/index.ts b/packages/base/src/localSuggestionsManager/index.ts new file mode 100644 index 0000000..e999a27 --- /dev/null +++ b/packages/base/src/localSuggestionsManager/index.ts @@ -0,0 +1,2 @@ +import { LocalSuggestionsManager } from './localSuggestionsManager'; +export { LocalSuggestionsManager }; diff --git a/packages/base/src/localSuggestionsManager/localSuggestionsManager.ts b/packages/base/src/localSuggestionsManager/localSuggestionsManager.ts new file mode 100644 index 0000000..0c27f71 --- /dev/null +++ b/packages/base/src/localSuggestionsManager/localSuggestionsManager.ts @@ -0,0 +1,103 @@ +import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook'; +import { + IAllSuggestions, + ISuggestionChange, + ISuggestionsManager +} from '../types'; +import { ISignal, Signal } from '@lumino/signaling'; +import { Cell, ICellModel } from '@jupyterlab/cells'; +import { UUID } from '@lumino/coreutils'; +import { ICell } from '@jupyterlab/nbformat'; +// import { ICell } from '@jupyterlab/nbformat'; +export class LocalSuggestionsManager implements ISuggestionsManager { + constructor(options: LocalSuggestionsManager.IOptions) { + this._tracker = options.tracker; + this._tracker.widgetAdded.connect(this._notebookAdded, this); + } + + get isDisposed(): boolean { + return this._isDisposed; + } + + get suggestionChanged(): ISignal { + return this._suggestionChanged; + } + dispose(): void { + if (this._isDisposed) { + return; + } + this._tracker.widgetAdded.disconnect(this._notebookAdded); + Signal.clearData(this); + this._isDisposed = true; + } + + getAllSuggestions(notebook: NotebookPanel): IAllSuggestions | undefined { + const path = notebook.context.localPath; + if (this._suggestionsMap.has(path)) { + return this._suggestionsMap.get(path); + } + // TODO Read suggestions from metadata + } + + getSuggestion(options: { + notebookPath: string; + cellId: string; + suggestionId: string; + }): { content: ICell } | undefined { + const { notebookPath, cellId, suggestionId } = options; + if (this._suggestionsMap.has(notebookPath)) { + const nbSuggestions = this._suggestionsMap.get(notebookPath); + if (nbSuggestions && nbSuggestions.has(cellId)) { + return nbSuggestions.get(cellId)![suggestionId]; + } + } + // TODO Read suggestions from metadata + } + async addSuggestion(options: { + notebook: NotebookPanel; + cell: Cell; + }): Promise { + const { notebook, cell } = options; + const path = notebook.context.localPath; + if (!this._suggestionsMap.has(path)) { + this._suggestionsMap.set(path, new Map()); + } + const currentSuggestions = this._suggestionsMap.get(path)!; + const cellId = cell.model.id; + if (!currentSuggestions.has(cellId)) { + currentSuggestions.set(cellId, {}); + } + const cellSuggesions = currentSuggestions.get(cellId)!; + const suggestionId = UUID.uuid4(); + cellSuggesions[suggestionId] = { content: cell.model.toJSON() }; + this._suggestionChanged.emit({ + notebookPath: path, + cellId, + suggestionId, + operator: 'added' + }); + return suggestionId; + } + + private _notebookAdded(tracker: INotebookTracker, panel: NotebookPanel) { + panel.disposed.connect(p => { + const localPath = p.context.localPath; + if (this._suggestionsMap.has(localPath)) { + // this._suggestionsMap.delete(localPath); + } + }); + } + private _suggestionChanged = new Signal< + ISuggestionsManager, + ISuggestionChange + >(this); + private _isDisposed = false; + private _tracker: INotebookTracker; + private _suggestionsMap = new Map(); +} + +export namespace LocalSuggestionsManager { + export interface IOptions { + tracker: INotebookTracker; + } +} diff --git a/packages/base/src/suggestionsPanel/cellWidget.ts b/packages/base/src/suggestionsPanel/cellWidget.ts new file mode 100644 index 0000000..75c5eae --- /dev/null +++ b/packages/base/src/suggestionsPanel/cellWidget.ts @@ -0,0 +1,106 @@ +import { Cell, CodeCell, CodeCellModel } from '@jupyterlab/cells'; +import { ICell } from '@jupyterlab/nbformat'; +import { + RenderMimeRegistry, + standardRendererFactories as initialFactories +} from '@jupyterlab/rendermime'; +import { Panel } from '@lumino/widgets'; +import { suggestionCellStyle } from './style'; +import { + CodeMirrorEditorFactory, + EditorExtensionRegistry, + EditorLanguageRegistry, + EditorThemeRegistry, + ybinding +} from '@jupyterlab/codemirror'; +import { IYText } from '@jupyter/ydoc'; + +export class CellWidget extends Panel { + constructor(options: CellWidget.IOptions) { + super(options); + const { cellModel } = options; + this.addClass(suggestionCellStyle); + + const editorExtensions = () => { + const themes = new EditorThemeRegistry(); + EditorThemeRegistry.getDefaultThemes().forEach(theme => { + themes.addTheme(theme); + }); + const registry = new EditorExtensionRegistry(); + + EditorExtensionRegistry.getDefaultExtensions({ themes }).forEach( + extensionFactory => { + registry.addExtension(extensionFactory); + } + ); + registry.addExtension({ + name: 'shared-model-binding', + factory: options => { + const sharedModel = options.model.sharedModel as IYText; + return EditorExtensionRegistry.createImmutableExtension( + ybinding({ + ytext: sharedModel.ysource, + undoManager: sharedModel.undoManager ?? undefined + }) + ); + } + }); + return registry; + }; + + const rendermime = new RenderMimeRegistry({ initialFactories }); + const languages = new EditorLanguageRegistry(); + EditorLanguageRegistry.getDefaultLanguages() + .filter(language => + ['ipython', 'julia', 'python'].includes(language.name.toLowerCase()) + ) + .forEach(language => { + languages.addLanguage(language); + }); + + languages.addLanguage({ + name: 'ipythongfm', + mime: 'text/x-ipythongfm', + load: async () => { + const m = await import('@codemirror/lang-markdown'); + return m.markdown({ + codeLanguages: (info: string) => languages.findBest(info) as any + }); + } + }); + const factoryService = new CodeMirrorEditorFactory({ + extensions: editorExtensions(), + languages + }); + const model = new CodeCellModel(); + let mimeType = 'text/plain'; + if (cellModel.cell_type === 'code') { + //TODO Detect correct kernel language + mimeType = 'text/x-ipython'; + } else if (cellModel.cell_type === 'markdown') { + mimeType = 'text/x-ipythongfm'; + } + model.mimeType = mimeType; + model.sharedModel.setSource(options.cellModel.source as string); + const cellWidget = new CodeCell({ + contentFactory: new Cell.ContentFactory({ + editorFactory: factoryService.newInlineEditor.bind(factoryService) + }), + rendermime, + model, + editorConfig: { + lineNumbers: false, + lineWrap: false, + matchBrackets: true, + tabFocusable: false + } + }).initializeState(); + this.addWidget(cellWidget); + } +} + +export namespace CellWidget { + export interface IOptions extends Panel.IOptions { + cellModel: ICell; + } +} diff --git a/packages/base/src/suggestionsPanel/model.ts b/packages/base/src/suggestionsPanel/model.ts index 5d58dd8..dfb15e9 100644 --- a/packages/base/src/suggestionsPanel/model.ts +++ b/packages/base/src/suggestionsPanel/model.ts @@ -1,5 +1,104 @@ -import { ISuggestionsModel } from '../types'; - +import { NotebookPanel } from '@jupyterlab/notebook'; +import { + IAllSuggestions, + ISuggestionChange, + ISuggestionsManager, + ISuggestionsModel +} from '../types'; +import { ISignal, Signal } from '@lumino/signaling'; export class SuggestionsModel implements ISuggestionsModel { - filePath = ''; + constructor(options: SuggestionsModel.IOptions) { + this.switchNotebook(options.panel); + this._suggestionsManager = options.suggestionsManager; + this._suggestionsManager.suggestionChanged.connect( + this._handleSuggestionChanged, + this + ); + } + get filePath(): string { + return this._filePath ?? '-'; + } + get notebookSwitched(): ISignal { + return this._notebookSwitched; + } + get suggestionChanged(): ISignal< + ISuggestionsModel, + Omit + > { + return this._suggestionChanged; + } + get currentNotebookPanel(): NotebookPanel | null { + return this._notebookPanel; + } + get isDisposed(): boolean { + return this._isDisposed; + } + get allSuggestions(): IAllSuggestions | undefined { + return this._allSuggestions; + } + dispose(): void { + if (this._isDisposed) { + return; + } + this._isDisposed = true; + this._suggestionsManager.suggestionChanged.disconnect( + this._handleSuggestionChanged + ); + Signal.clearData(this); + } + + async addSuggestion(): Promise { + const activeCell = this._notebookPanel?.content.activeCell; + if (activeCell && this._notebookPanel) { + await this._suggestionsManager.addSuggestion({ + notebook: this._notebookPanel, + cell: activeCell + }); + } + } + getSuggestion(options: { cellId: string; suggestionId: string }) { + if (!this._filePath) { + return; + } + return this._suggestionsManager.getSuggestion({ + notebookPath: this._filePath, + ...options + }); + } + async switchNotebook(panel: NotebookPanel | null): Promise { + if (panel) { + await panel.context.ready; + this._allSuggestions = this._suggestionsManager.getAllSuggestions(panel); + } + this._notebookPanel = panel; + this._filePath = this._notebookPanel?.context.localPath; + this._notebookSwitched.emit(); + } + + private _handleSuggestionChanged( + manager: ISuggestionsManager, + changed: ISuggestionChange + ) { + const { notebookPath, ...newChanged } = changed; + if (notebookPath === this._filePath) { + this._suggestionChanged.emit(newChanged); + } + } + private _isDisposed = false; + private _filePath?: string; + private _notebookPanel: NotebookPanel | null = null; + private _notebookSwitched: Signal = new Signal(this); + private _allSuggestions?: IAllSuggestions; + private _suggestionsManager: ISuggestionsManager; + private _suggestionChanged = new Signal< + ISuggestionsModel, + Omit + >(this); +} + +export namespace SuggestionsModel { + export interface IOptions { + panel: NotebookPanel | null; + suggestionsManager: ISuggestionsManager; + } } diff --git a/packages/base/src/suggestionsPanel/panel.ts b/packages/base/src/suggestionsPanel/panel.ts index aed216a..db9eaaa 100644 --- a/packages/base/src/suggestionsPanel/panel.ts +++ b/packages/base/src/suggestionsPanel/panel.ts @@ -1,42 +1,53 @@ -import { IWidgetTracker } from '@jupyterlab/apputils'; -import { IDocumentWidget } from '@jupyterlab/docregistry'; import { SidePanel } from '@jupyterlab/ui-components'; +import { Signal } from '@lumino/signaling'; import { ISuggestionsModel } from '../types'; import { SuggestionsPanelHeader } from './header'; +import { mainPanelStyle } from './style'; +import { SuggestionsWidget } from './widget'; export class SuggestionsPanelWidget extends SidePanel { constructor(options: SuggestionsPanelWidget.IOptions) { - super(); - this.addClass('jp-suggestions-sidepanel-widget'); + super(options); + this.addClass(mainPanelStyle); this.node.tabIndex = 0; this._model = options.model; + this.header.addWidget(this._headerWidget); - const header = new SuggestionsPanelHeader(); - this.header.addWidget(header); - options.tracker.currentChanged.connect(async (_, changed) => { - if (changed) { - header.title.label = changed.context.localPath; - await changed.context.ready; - } else { - header.title.label = '-'; - } - }); + this.connectSignal(); + this._handleNotebookSwitched(); + const widget = new SuggestionsWidget({ model: this._model }); + this.addWidget(widget); } - get model(): ISuggestionsModel { - return this._model; + connectSignal() { + this._model.notebookSwitched.connect(this._handleNotebookSwitched, this); + } + + disconnectSignal() { + this._model.notebookSwitched.disconnect(this._handleNotebookSwitched); + Signal.clearData(this); } dispose(): void { + if (this.isDisposed) { + return; + } + this.disconnectSignal(); super.dispose(); } + + private _handleNotebookSwitched() { + const filePath = this._model.filePath; + this._headerWidget.title.label = filePath; + } + private _model: ISuggestionsModel; + private _headerWidget = new SuggestionsPanelHeader(); } export namespace SuggestionsPanelWidget { - export interface IOptions { + export interface IOptions extends SidePanel.IOptions { model: ISuggestionsModel; - tracker: IWidgetTracker; } } diff --git a/packages/base/src/suggestionsPanel/style.ts b/packages/base/src/suggestionsPanel/style.ts new file mode 100644 index 0000000..1ef00d5 --- /dev/null +++ b/packages/base/src/suggestionsPanel/style.ts @@ -0,0 +1,13 @@ +import { style } from 'typestyle'; + +export const mainPanelStyle = style({}); +export const suggestionsWidgetAreaStyle = style({ + display: 'flex', + flexDirection: 'column', + gap: '10px', + height: '100%' +}); + +export const suggestionCellStyle = style({ + background: 'var(--jp-cell-editor-background)' +}); diff --git a/packages/base/src/suggestionsPanel/widget.ts b/packages/base/src/suggestionsPanel/widget.ts new file mode 100644 index 0000000..5912f6b --- /dev/null +++ b/packages/base/src/suggestionsPanel/widget.ts @@ -0,0 +1,74 @@ +import { PanelWithToolbar } from '@jupyterlab/ui-components'; +import { Panel } from '@lumino/widgets'; + +import { ISuggestionChange, ISuggestionsModel } from '../types'; +import { CellWidget } from './cellWidget'; +import { suggestionsWidgetAreaStyle } from './style'; + +export class SuggestionsWidget extends PanelWithToolbar { + constructor(options: SuggestionsWidget.IOptions) { + super(options); + this.title.label = 'All Suggestions'; + this._model = options.model; + this._suggestionsArea.addClass(suggestionsWidgetAreaStyle); + this.addWidget(this._suggestionsArea); + + this._renderSuggestions(); + + this._model.notebookSwitched.connect(() => { + this._renderSuggestions(); + }); + this._model.suggestionChanged.connect(this._updateSuggestions, this); + + this._model.notebookSwitched.connect(this._handleNotebookSwitched, this); + } + + private _updateSuggestions( + _: ISuggestionsModel, + changedArg: Omit + ) { + const { operator, cellId, suggestionId } = changedArg; + switch (operator) { + case 'added': { + const suggestion = this._model.getSuggestion({ cellId, suggestionId }); + if (suggestion) { + const w = new CellWidget({ cellModel: suggestion.content }); + w.id = suggestionId; + this._suggestionsArea.addWidget(w); + } + break; + } + + default: + break; + } + } + + private _handleNotebookSwitched() { + this._renderSuggestions(); + } + private _renderSuggestions() { + const allSuggestions = this._model.allSuggestions; + const allWidgets = [...this._suggestionsArea.widgets]; + for (const element of allWidgets) { + element.parent = null; + } + if (allSuggestions) { + for (const val of allSuggestions.values()) { + Object.entries(val).forEach(([suggestionId, suggestionDef]) => { + const w = new CellWidget({ cellModel: suggestionDef.content }); + w.id = suggestionId; + this._suggestionsArea.addWidget(w); + }); + } + } + } + private _suggestionsArea = new Panel(); + private _model: ISuggestionsModel; +} + +export namespace SuggestionsWidget { + export interface IOptions extends PanelWithToolbar.IOptions { + model: ISuggestionsModel; + } +} diff --git a/packages/base/src/tokens.ts b/packages/base/src/tokens.ts new file mode 100644 index 0000000..909e11c --- /dev/null +++ b/packages/base/src/tokens.ts @@ -0,0 +1,10 @@ +import { Token } from '@lumino/coreutils'; +import { ISuggestionsManager, ISuggestionsModel } from './types'; + +export const ISuggestionsModelToken = new Token( + 'jupyter-suggestions:suggestionsModel' +); + +export const ISuggestionsManagerToken = new Token( + 'jupyter-suggestions:suggestionsManager' +); diff --git a/packages/base/src/types.ts b/packages/base/src/types.ts index 3c105c4..e88f313 100644 --- a/packages/base/src/types.ts +++ b/packages/base/src/types.ts @@ -1,7 +1,47 @@ +import { NotebookPanel } from '@jupyterlab/notebook'; +import { ISignal } from '@lumino/signaling'; +import { IDisposable } from '@lumino/disposable'; +import { Cell, ICellModel } from '@jupyterlab/cells'; +import { ICell } from '@jupyterlab/nbformat'; export interface IDict { [key: string]: T; } -export interface ISuggestionsModel { - filePath?: string; +export interface ISuggestionsModel extends IDisposable { + filePath: string; + currentNotebookPanel: NotebookPanel | null; + allSuggestions: IAllSuggestions | undefined; + notebookSwitched: ISignal; + suggestionChanged: ISignal< + ISuggestionsModel, + Omit + >; + switchNotebook(panel: NotebookPanel | null): Promise; + addSuggestion(): void; + getSuggestion(options: { + cellId: string; + suggestionId: string; + }): { content: ICell } | undefined; +} + +export interface ISuggestionChange { + notebookPath: string; + cellId: string; + operator: 'added' | 'deleted' | 'modified'; + suggestionId: string; +} +export type IAllSuggestions = Map>; + +export interface ISuggestionsManager extends IDisposable { + getAllSuggestions(notebook: NotebookPanel): IAllSuggestions | undefined; + addSuggestion(options: { + notebook: NotebookPanel; + cell: Cell; + }): Promise; + suggestionChanged: ISignal; + getSuggestion(options: { + notebookPath: string; + cellId: string; + suggestionId: string; + }): { content: ICell } | undefined; } diff --git a/python/jupyter_suggestions_core/package.json b/python/jupyter_suggestions_core/package.json index 9990bc3..27a30e2 100644 --- a/python/jupyter_suggestions_core/package.json +++ b/python/jupyter_suggestions_core/package.json @@ -75,7 +75,12 @@ }, "extension": true, "outputDir": "jupyter_suggestions_core/labextension", - "sharedPackages": {}, + "sharedPackages": { + "@jupyter/jupyter-suggestions-base": { + "singleton": true, + "bundled": true + } + }, "schemaDir": "schema" } } diff --git a/python/jupyter_suggestions_core/src/index.ts b/python/jupyter_suggestions_core/src/index.ts index ba50ecb..ea9eef3 100644 --- a/python/jupyter_suggestions_core/src/index.ts +++ b/python/jupyter_suggestions_core/src/index.ts @@ -1,7 +1,13 @@ import { suggestionsModelPlugin, suggestionsPanelPlugin, - commandsPlugin + commandsPlugin, + suggestionsManagerPlugin } from './plugins'; -export default [suggestionsModelPlugin, suggestionsPanelPlugin, commandsPlugin]; +export default [ + suggestionsModelPlugin, + suggestionsPanelPlugin, + commandsPlugin, + suggestionsManagerPlugin +]; diff --git a/python/jupyter_suggestions_core/src/plugins.ts b/python/jupyter_suggestions_core/src/plugins.ts index 3341f80..595d6c2 100644 --- a/python/jupyter_suggestions_core/src/plugins.ts +++ b/python/jupyter_suggestions_core/src/plugins.ts @@ -1,8 +1,12 @@ import { hintIcon, ISuggestionsModel, + ISuggestionsModelToken, SuggestionsModel, - SuggestionsPanelWidget + SuggestionsPanelWidget, + LocalSuggestionsManager, + ISuggestionsManager, + ISuggestionsManagerToken } from '@jupyter/jupyter-suggestions-base'; import { ILayoutRestorer, @@ -10,26 +14,35 @@ import { JupyterFrontEndPlugin } from '@jupyterlab/application'; import { INotebookTracker } from '@jupyterlab/notebook'; - -import { ISuggestionsModelToken } from './tokens'; +import { ITranslator, nullTranslator } from '@jupyterlab/translation'; const NAME_SPACE = '@jupyter/jupyter-suggestions-core'; export const suggestionsModelPlugin: JupyterFrontEndPlugin = { - id: `${NAME_SPACE}:suggestion-model`, + id: `${NAME_SPACE}:model`, description: 'The model of the suggestions panel', autoStart: true, - requires: [ILayoutRestorer, INotebookTracker], + requires: [INotebookTracker, ISuggestionsManagerToken], provides: ISuggestionsModelToken, activate: ( app: JupyterFrontEnd, - restorer: ILayoutRestorer, - tracker: INotebookTracker + tracker: INotebookTracker, + suggestionsManager: ISuggestionsManager ): ISuggestionsModel => { - console.log(`${NAME_SPACE}:suggestion-model is activated`); - const model = new SuggestionsModel(); - + console.log(`${NAME_SPACE}:model is activated`); + const model = new SuggestionsModel({ + panel: tracker.currentWidget, + suggestionsManager + }); + tracker.currentChanged.connect(async (_, changed) => { + if (changed) { + await changed.context.ready; + model.switchNotebook(changed); + } else { + model.switchNotebook(null); + } + }); return model; } }; @@ -45,35 +58,55 @@ export const commandsPlugin: JupyterFrontEndPlugin = { id: `${NAME_SPACE}:commands`, description: 'A JupyterLab extension for suggesting changes.', autoStart: true, - requires: [ISuggestionsModelToken], - activate: (app: JupyterFrontEnd, model: ISuggestionsModel) => { - console.log(`${NAME_SPACE}:suggestion-commands is activated`); + requires: [INotebookTracker, ISuggestionsModelToken], + optional: [ITranslator], + activate: ( + app: JupyterFrontEnd, + tracker: INotebookTracker, + model: ISuggestionsModel, + translator_: ITranslator | null + ) => { + console.log(`${NAME_SPACE}:commands is activated`); const { commands } = app; + const translator = translator_ ?? nullTranslator; + const trans = translator.load('jupyterlab'); commands.addCommand(COMMAND_IDS.addCellSuggestion, { icon: hintIcon, - caption: 'Add suggestion', - execute: () => { - // + caption: trans.__('Add suggestion'), + execute: async () => { + const current = tracker.currentWidget; + if (current !== model.currentNotebookPanel) { + await model.switchNotebook(current); + } + await model.addSuggestion(); }, isVisible: () => true }); + tracker.activeCellChanged.connect(() => { + commands.notifyCommandChanged(COMMAND_IDS.addCellSuggestion); + }); + tracker.selectionChanged.connect(() => { + commands.notifyCommandChanged(COMMAND_IDS.addCellSuggestion); + }); } }; export const suggestionsPanelPlugin: JupyterFrontEndPlugin = { - id: `${NAME_SPACE}:suggestion-panel`, + id: `${NAME_SPACE}:panel`, description: 'A JupyterLab extension for suggesting changes.', autoStart: true, - requires: [ISuggestionsModelToken, ILayoutRestorer, INotebookTracker], + requires: [ISuggestionsModelToken, ILayoutRestorer], + optional: [ITranslator], activate: ( app: JupyterFrontEnd, model: ISuggestionsModel, restorer: ILayoutRestorer, - tracker: INotebookTracker + translator_: ITranslator | null ) => { - console.log(`${NAME_SPACE}:suggestion-panel is activated`); - const panel = new SuggestionsPanelWidget({ model, tracker }); - panel.id = 'jupyter-suggestions::main-panel'; + console.log(`${NAME_SPACE}:panel is activated`); + const translator = translator_ ?? nullTranslator; + const panel = new SuggestionsPanelWidget({ model, translator }); + panel.id = 'jupyter-suggestions:main-panel'; panel.title.caption = 'Jupyter Suggestions'; panel.title.icon = hintIcon; if (restorer) { @@ -82,3 +115,16 @@ export const suggestionsPanelPlugin: JupyterFrontEndPlugin = { app.shell.add(panel, 'right', { rank: 2000, activate: false }); } }; + +export const suggestionsManagerPlugin: JupyterFrontEndPlugin = + { + id: `${NAME_SPACE}:manager`, + description: 'A JupyterLab extension for suggesting changes.', + autoStart: true, + requires: [INotebookTracker], + provides: ISuggestionsManagerToken, + activate: (app: JupyterFrontEnd, tracker: INotebookTracker) => { + const manager = new LocalSuggestionsManager({ tracker }); + return manager; + } + }; diff --git a/python/jupyter_suggestions_core/src/tokens.ts b/python/jupyter_suggestions_core/src/tokens.ts deleted file mode 100644 index 7b4fd0c..0000000 --- a/python/jupyter_suggestions_core/src/tokens.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Token } from '@lumino/coreutils'; -import { ISuggestionsModel } from '@jupyter/jupyter-suggestions-base'; - -export const ISuggestionsModelToken = new Token( - 'jupyter-suggestions:suggestionsModel' -); diff --git a/yarn.lock b/yarn.lock index ada7676..6db0d9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -490,12 +490,22 @@ __metadata: version: 0.0.0-use.local resolution: "@jupyter/jupyter-suggestions-base@workspace:packages/base" dependencies: + "@jupyter/ydoc": ^3.0.0 "@jupyterlab/apputils": ^4.0.0 + "@jupyterlab/cells": ^4.0.0 + "@jupyterlab/codemirror": ^4.0.0 "@jupyterlab/docregistry": ^4.0.0 + "@jupyterlab/nbformat": ^4.0.0 + "@jupyterlab/notebook": ^4.0.0 + "@jupyterlab/rendermime": ^4.0.0 "@jupyterlab/ui-components": ^4.0.0 + "@lumino/coreutils": ^2.0.0 + "@lumino/disposable": ^2.0.0 + "@lumino/signaling": ^2.0.0 "@lumino/widgets": ^2.0.0 rimraf: ^3.0.2 typescript: ^5 + typestyle: ^2.4.0 languageName: unknown linkType: soft @@ -691,7 +701,7 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/cells@npm:^4.3.1": +"@jupyterlab/cells@npm:^4.0.0, @jupyterlab/cells@npm:^4.3.1": version: 4.3.1 resolution: "@jupyterlab/cells@npm:4.3.1" dependencies: @@ -751,7 +761,7 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/codemirror@npm:^4.3.1": +"@jupyterlab/codemirror@npm:^4.0.0, @jupyterlab/codemirror@npm:^4.3.1": version: 4.3.1 resolution: "@jupyterlab/codemirror@npm:4.3.1" dependencies: @@ -928,7 +938,7 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/nbformat@npm:^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0, @jupyterlab/nbformat@npm:^4.3.1": +"@jupyterlab/nbformat@npm:^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0, @jupyterlab/nbformat@npm:^4.0.0, @jupyterlab/nbformat@npm:^4.3.1": version: 4.3.1 resolution: "@jupyterlab/nbformat@npm:4.3.1" dependencies: @@ -1020,7 +1030,7 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/rendermime@npm:^4.3.1": +"@jupyterlab/rendermime@npm:^4.0.0, @jupyterlab/rendermime@npm:^4.3.1": version: 4.3.1 resolution: "@jupyterlab/rendermime@npm:4.3.1" dependencies: @@ -1457,7 +1467,7 @@ __metadata: languageName: node linkType: hard -"@lumino/coreutils@npm:^1.11.0 || ^2.0.0, @lumino/coreutils@npm:^1.11.0 || ^2.2.0, @lumino/coreutils@npm:^2.2.0": +"@lumino/coreutils@npm:^1.11.0 || ^2.0.0, @lumino/coreutils@npm:^1.11.0 || ^2.2.0, @lumino/coreutils@npm:^2.0.0, @lumino/coreutils@npm:^2.2.0": version: 2.2.0 resolution: "@lumino/coreutils@npm:2.2.0" dependencies: @@ -1466,7 +1476,7 @@ __metadata: languageName: node linkType: hard -"@lumino/disposable@npm:^1.10.0 || ^2.0.0, @lumino/disposable@npm:^2.1.3": +"@lumino/disposable@npm:^1.10.0 || ^2.0.0, @lumino/disposable@npm:^2.0.0, @lumino/disposable@npm:^2.1.3": version: 2.1.3 resolution: "@lumino/disposable@npm:2.1.3" dependencies: @@ -1527,7 +1537,7 @@ __metadata: languageName: node linkType: hard -"@lumino/signaling@npm:^1.10.0 || ^2.0.0, @lumino/signaling@npm:^2.1.3": +"@lumino/signaling@npm:^1.10.0 || ^2.0.0, @lumino/signaling@npm:^2.0.0, @lumino/signaling@npm:^2.1.3": version: 2.1.3 resolution: "@lumino/signaling@npm:2.1.3" dependencies: @@ -9672,7 +9682,7 @@ __metadata: "typescript@patch:typescript@>=3 < 6#~builtin, typescript@patch:typescript@^5#~builtin": version: 5.6.3 - resolution: "typescript@patch:typescript@npm%3A5.6.3#~builtin::version=5.6.3&hash=85af82" + resolution: "typescript@patch:typescript@npm%3A5.6.3#~builtin::version=5.6.3&hash=e012d7" bin: tsc: bin/tsc tsserver: bin/tsserver @@ -9682,15 +9692,15 @@ __metadata: "typescript@patch:typescript@~5.0.2#~builtin": version: 5.0.4 - resolution: "typescript@patch:typescript@npm%3A5.0.4#~builtin::version=5.0.4&hash=85af82" + resolution: "typescript@patch:typescript@npm%3A5.0.4#~builtin::version=5.0.4&hash=b5f058" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: bb309d320c59a26565fb3793dba550576ab861018ff3fd1b7fccabbe46ae4a35546bc45f342c0a0b6f265c801ccdf64ffd68f548f117ceb7f0eac4b805cd52a9 + checksum: d26b6ba97b6d163c55dbdffd9bbb4c211667ebebc743accfeb2c8c0154aace7afd097b51165a72a5bad2cf65a4612259344ff60f8e642362aa1695c760d303ac languageName: node linkType: hard -"typestyle@npm:^2.0.4": +"typestyle@npm:^2.0.4, typestyle@npm:^2.4.0": version: 2.4.0 resolution: "typestyle@npm:2.4.0" dependencies: