Skip to content

Commit

Permalink
Update interface
Browse files Browse the repository at this point in the history
  • Loading branch information
trungleduc committed Dec 7, 2024
1 parent ba7974c commit 6aee021
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
import {
IAllSuggestions,
IDict,
ISerializedSuggessionData,
ISuggestionChange,
ISuggestionData,
ISuggestionsManager
} from '../types';
import { ISignal, Signal } from '@lumino/signaling';
import { Cell, ICellModel } from '@jupyterlab/cells';
import {
Cell,
CodeCellModel,
ICellModel,
ICodeCellModel
} from '@jupyterlab/cells';
import { UUID } from '@lumino/coreutils';
import { ICell } from '@jupyterlab/nbformat';

const METADATA_KEY = 'jupyter_suggestion';
export class LocalSuggestionsManager implements ISuggestionsManager {
Expand Down Expand Up @@ -40,10 +47,21 @@ export class LocalSuggestionsManager implements ISuggestionsManager {
if (this._suggestionsMap.has(path)) {
return this._suggestionsMap.get(path);
} else {
const savedSuggestions = notebook.context.model.getMetadata(METADATA_KEY);
const savedSuggestions: IDict<IDict<ISerializedSuggessionData>> =
notebook.context.model.getMetadata(METADATA_KEY);
if (savedSuggestions) {
const currentSuggestion = new Map<string, IDict<ISuggestionData>>(
Object.entries(savedSuggestions)
const currentSuggestion = new Map<string, IDict<ISuggestionData>>();

Object.entries(savedSuggestions).forEach(
([cellID, serializedCellSuggestions]) => {
const data: IDict<ISuggestionData> = {};
Object.entries(serializedCellSuggestions).forEach(
([id, serializedData]) => {
data[id] = this._deserializedSuggestion(serializedData);
}
);
currentSuggestion.set(cellID, data);
}
);
this._suggestionsMap.set(path, currentSuggestion);
return currentSuggestion;
Expand Down Expand Up @@ -80,17 +98,17 @@ export class LocalSuggestionsManager implements ISuggestionsManager {
}
const cellSuggesions = currentSuggestions.get(cellId)!;
const suggestionId = UUID.uuid4();
const cellModel = cell.model.toJSON();
const icellModel = cell.model.toJSON();
const suggestionContent: ISuggestionData = {
content: cellModel,
newSource: cellModel.source as string
originalICell: icellModel,
cellModel: this._cloneCellModel(icellModel)
};
cellSuggesions[suggestionId] = suggestionContent;
await this._saveSuggestionToMetadata({
notebook,
cellId,
suggestionId,
content: suggestionContent
suggestionContent
});
this._suggestionChanged.emit({
notebookPath: path,
Expand All @@ -115,7 +133,7 @@ export class LocalSuggestionsManager implements ISuggestionsManager {
suggestionId
});
if (currentSuggestion && notebook.content.model?.cells) {
const { newSource } = currentSuggestion;
const newSource = currentSuggestion.cellModel.toJSON().source as string;
for (const element of notebook.content.model.cells) {
if (element.id === cellId) {
element.sharedModel.setSource(newSource);
Expand Down Expand Up @@ -168,8 +186,6 @@ export class LocalSuggestionsManager implements ISuggestionsManager {
nbSuggestions.has(cellId) &&
nbSuggestions.get(cellId)![suggestionId]
) {
const currentSuggestion = nbSuggestions.get(cellId)![suggestionId];
currentSuggestion.newSource = newSource;
await this._updateSuggestionInMetadata({
notebook,
cellId,
Expand All @@ -189,17 +205,20 @@ export class LocalSuggestionsManager implements ISuggestionsManager {
notebook: NotebookPanel;
cellId: string;
suggestionId: string;
content: IDict;
suggestionContent: ISuggestionData;
}) {
const { notebook, cellId, suggestionId, content } = options;
const currentSuggestions: IDict =
const { notebook, cellId, suggestionId, suggestionContent } = options;
const currentSuggestions: IDict<IDict<ISerializedSuggessionData>> =
notebook.context.model.getMetadata(METADATA_KEY) ?? {};

const serializedData: ISerializedSuggessionData = {
originalICell: suggestionContent.originalICell,
newSource: suggestionContent.cellModel.toJSON().source as string
};
const newData = {
...currentSuggestions,
[cellId]: {
...(currentSuggestions[cellId] ?? {}),
[suggestionId]: content
[suggestionId]: serializedData
}
};
notebook.context.model.setMetadata(METADATA_KEY, newData);
Expand All @@ -220,6 +239,9 @@ export class LocalSuggestionsManager implements ISuggestionsManager {
if (currentSuggestions[cellId][suggestionId]) {
delete currentSuggestions[cellId][suggestionId];
}
if (Object.keys(currentSuggestions[cellId]).length === 0) {
delete currentSuggestions[cellId];
}
notebook.context.model.setMetadata(METADATA_KEY, currentSuggestions);
await notebook.context.save();
}
Expand All @@ -231,8 +253,9 @@ export class LocalSuggestionsManager implements ISuggestionsManager {
newSource: string;
}) {
const { notebook, cellId, suggestionId, newSource } = options;
const currentSuggestions: IDict<IDict<ISuggestionData>> | undefined =
notebook.context.model.getMetadata(METADATA_KEY);
const currentSuggestions:
| IDict<IDict<{ content: ICell; newSource: string }>>
| undefined = notebook.context.model.getMetadata(METADATA_KEY);
if (
!currentSuggestions ||
!currentSuggestions[cellId] ||
Expand All @@ -255,6 +278,40 @@ export class LocalSuggestionsManager implements ISuggestionsManager {
}
});
}

private _cloneCellModel(
cellModel: ICell,
newSource?: string
): ICodeCellModel {
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';
}
const copiedCellModel = new CodeCellModel();
copiedCellModel.mimeType = mimeType;
copiedCellModel.sharedModel.setSource(
newSource ?? (cellModel.source as string)
);
return copiedCellModel;
}

private _deserializedSuggestion(
serializedData: ISerializedSuggessionData
): ISuggestionData {
const newICell = JSON.parse(JSON.stringify(serializedData.originalICell));

const newCellModel = this._cloneCellModel(
newICell,
serializedData.newSource
);
return {
originalICell: newICell,
cellModel: newCellModel
};
}
private _suggestionChanged = new Signal<
ISuggestionsManager,
ISuggestionChange
Expand Down
1 change: 1 addition & 0 deletions packages/base/src/suggestionsPanel/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class SuggestionsModel implements ISuggestionsModel {
return this._isDisposed;
}
get allSuggestions(): IAllSuggestions | undefined {
console.log('aaaa', this._allSuggestions);
return this._allSuggestions;
}
dispose(): void {
Expand Down
52 changes: 13 additions & 39 deletions packages/base/src/suggestionsPanel/suggestionWidget/cellWidget.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Debouncer } from '@lumino/polling';
import { ISharedCodeCell, IYText } from '@jupyter/ydoc';
import { Cell, CodeCell, CodeCellModel } from '@jupyterlab/cells';
import { IYText } from '@jupyter/ydoc';
import { Cell, CodeCell, ICodeCellModel } from '@jupyterlab/cells';
import {
CodeMirrorEditorFactory,
EditorExtensionRegistry,
Expand All @@ -9,31 +8,28 @@ import {
ybinding
} from '@jupyterlab/codemirror';
import { ICell } from '@jupyterlab/nbformat';
import { ObservableMap } from '@jupyterlab/observables';
import {
RenderMimeRegistry,
standardRendererFactories as initialFactories
} from '@jupyterlab/rendermime';
import { JSONValue } from '@lumino/coreutils';
import { Signal } from '@lumino/signaling';
import { Panel } from '@lumino/widgets';
import { ObservableMap } from '@jupyterlab/observables';

import { ISuggestionData } from '../../types';
import { diffTextExtensionFactory } from '../cmExtension';
import { suggestionCellStyle } from './style';
import { SuggestionToolbar } from './suggestionToolbar';
import { JSONValue } from '@lumino/coreutils';
import { Signal } from '@lumino/signaling';
import { ISuggestionData } from '../../types';

export class CellWidget extends Panel {
constructor(options: CellWidget.IOptions) {
super(options);
const { suggestionData } = options;
const { content: cellModel, newSource } = suggestionData;
const { originalICell, cellModel } = suggestionData;
this.addClass(suggestionCellStyle);
this._cellId = cellModel.id as string | undefined;
this._cellWidget = this._createCell(
cellModel,
newSource,
options.updateCallback
);
this._cellWidget = this._createCell(originalICell, cellModel);
const toolbar = new SuggestionToolbar({
toggleMinimized: this.toggleMinimized.bind(this),
deleteCallback: options.deleteCallback,
Expand Down Expand Up @@ -119,33 +115,20 @@ export class CellWidget extends Panel {
});
return languages;
}
private _createCell(
cellModel: ICell,
newSource: string,
updateCallback: (content: string) => Promise<void>
) {
private _createCell(originalICell: ICell, cellModel: ICodeCellModel) {
const rendermime = new RenderMimeRegistry({ initialFactories });

const factoryService = new CodeMirrorEditorFactory({
extensions: this._cmExtensioRegistry(cellModel.source as string),
extensions: this._cmExtensioRegistry(originalICell.source as string),
languages: this._cmLanguageRegistry()
});
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(newSource);

const cellWidget = new CodeCell({
contentFactory: new Cell.ContentFactory({
editorFactory: factoryService.newInlineEditor.bind(factoryService)
}),
rendermime,
model,
model: cellModel,
editorConfig: {
lineNumbers: false,
lineWrap: false,
Expand All @@ -154,14 +137,6 @@ export class CellWidget extends Panel {
}
}).initializeState();

const debouncer = new Debouncer(async (cellModel: ISharedCodeCell) => {
const newContent = cellModel.toJSON();
await updateCallback(newContent.source as string);
}, 500);
model.sharedModel.changed.connect(async (cellModel, changed) => {
debouncer.invoke(cellModel);
});
model.sharedModel.disposed.connect(() => void debouncer.dispose());
return cellWidget;
}
private _state: ObservableMap<JSONValue> = new ObservableMap({
Expand All @@ -176,6 +151,5 @@ export namespace CellWidget {
suggestionData: ISuggestionData;
deleteCallback: () => Promise<void>;
acceptCallback: () => Promise<void>;
updateCallback: (content: string) => Promise<void>;
}
}
37 changes: 32 additions & 5 deletions packages/base/src/suggestionsPanel/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
import { CellWidget, suggestionCellSelectedStyle } from './suggestionWidget';
import { suggestionsWidgetAreaStyle } from './style';
import { Dialog, showDialog } from '@jupyterlab/apputils';
import { ISharedCodeCell } from '@jupyter/ydoc';
import { Debouncer } from '@lumino/polling';

export class SuggestionsWidget extends PanelWithToolbar {
constructor(options: SuggestionsWidget.IOptions) {
Expand Down Expand Up @@ -75,6 +77,13 @@ export class SuggestionsWidget extends PanelWithToolbar {
default:
break;
}

const count = this._suggestionsArea.widgets.length;
if (count && count !== 0) {
this.title.label = `All Suggestions (${count})`;
} else {
this.title.label = 'All Suggestions';
}
}

private _handleActiveCellChanged(
Expand Down Expand Up @@ -109,6 +118,12 @@ export class SuggestionsWidget extends PanelWithToolbar {
}
private _renderSuggestions() {
const allSuggestions = this._model.allSuggestions;
const count = allSuggestions?.size ?? 0;
if (count && count !== 0) {
this.title.label = `All Suggestions (${count})`;
} else {
this.title.label = 'All Suggestions';
}
const allWidgets = this._suggestionsArea.widgets;
for (const element of allWidgets) {
element.dispose();
Expand All @@ -132,7 +147,7 @@ export class SuggestionsWidget extends PanelWithToolbar {
suggestionData: ISuggestionData;
}): { widget: CellWidget; index: number } {
const { suggestionId, suggestionData } = options;
const cellId = suggestionData.content.id as string | undefined;
const cellId = suggestionData.originalICell.id as string | undefined;

const cellIdx = this._model.getCellIndex(cellId);

Expand All @@ -158,9 +173,22 @@ export class SuggestionsWidget extends PanelWithToolbar {
}
};

const updateCallback = async (newSource: string) => {
await this._model.updateSuggestion({ cellId, suggestionId, newSource });
};
const debouncer = new Debouncer(async (cellModel: ISharedCodeCell) => {
const newContent = cellModel.toJSON();
await this._model.updateSuggestion({
cellId,
suggestionId,
newSource: newContent.source as string
});
}, 500);
suggestionData.cellModel.sharedModel.changed.connect(
async (cellModel, changed) => {
debouncer.invoke(cellModel);
}
);
suggestionData.cellModel.sharedModel.disposed.connect(
() => void debouncer.dispose()
);

const acceptCallback = async () => {
const accepted = await this._model.acceptSuggestion({
Expand All @@ -182,7 +210,6 @@ export class SuggestionsWidget extends PanelWithToolbar {
const w = new CellWidget({
suggestionData,
deleteCallback,
updateCallback,
acceptCallback
});

Expand Down
10 changes: 7 additions & 3 deletions packages/base/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { NotebookPanel } from '@jupyterlab/notebook';
import { ISignal } from '@lumino/signaling';
import { IDisposable } from '@lumino/disposable';
import { Cell, ICellModel } from '@jupyterlab/cells';
import { Cell, ICellModel, ICodeCellModel } from '@jupyterlab/cells';
import { ICell } from '@jupyterlab/nbformat';
export interface IDict<T = any> {
[key: string]: T;
}
export interface ISuggestionData {
content: ICell;
newSource: string;
originalICell: ICell;
cellModel: ICodeCellModel;
}

export interface ISerializedSuggessionData {
originalICell: ICell;
newSource: string;
}
/**
* Interface defining the structure and behavior of a suggestions model.
*
Expand Down
Loading

0 comments on commit 6aee021

Please sign in to comment.