Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display cell in suggestion panel #5

Merged
merged 6 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion packages/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions packages/base/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './types';
export * from './suggestionsPanel';
export * from './icons';
export * from './tokens';
export * from './localSuggestionsManager';
2 changes: 2 additions & 0 deletions packages/base/src/localSuggestionsManager/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { LocalSuggestionsManager } from './localSuggestionsManager';
export { LocalSuggestionsManager };
103 changes: 103 additions & 0 deletions packages/base/src/localSuggestionsManager/localSuggestionsManager.ts
Original file line number Diff line number Diff line change
@@ -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<ISuggestionsManager, ISuggestionChange> {
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<ICellModel>;
}): Promise<string> {
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<string, IAllSuggestions>();
}

export namespace LocalSuggestionsManager {
export interface IOptions {
tracker: INotebookTracker;
}
}
106 changes: 106 additions & 0 deletions packages/base/src/suggestionsPanel/cellWidget.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
105 changes: 102 additions & 3 deletions packages/base/src/suggestionsPanel/model.ts
Original file line number Diff line number Diff line change
@@ -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<ISuggestionsModel, void> {
return this._notebookSwitched;
}
get suggestionChanged(): ISignal<
ISuggestionsModel,
Omit<ISuggestionChange, 'notebookPath'>
> {
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<void> {
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<void> {
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<this, void> = new Signal(this);
private _allSuggestions?: IAllSuggestions;
private _suggestionsManager: ISuggestionsManager;
private _suggestionChanged = new Signal<
ISuggestionsModel,
Omit<ISuggestionChange, 'notebookPath'>
>(this);
}

export namespace SuggestionsModel {
export interface IOptions {
panel: NotebookPanel | null;
suggestionsManager: ISuggestionsManager;
}
}
Loading
Loading