Skip to content

Commit

Permalink
Support cell deletion suggestions (#15)
Browse files Browse the repository at this point in the history
Co-authored-by: Jeremy Tuloup <[email protected]>
  • Loading branch information
trungleduc and jtpio authored Dec 19, 2024
1 parent bc16dbd commit fdd757e
Show file tree
Hide file tree
Showing 26 changed files with 914 additions and 208 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"@types/json-schema": "^7.0.11",
"@types/react": "^18.0.26",
"@types/react-addons-linked-state-mixin": "^0.14.22",
"@types/react-dom": "^18.0.26",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"css-loader": "^6.7.1",
Expand Down
8 changes: 5 additions & 3 deletions packages/base/src/baseSuggestionsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
IAllSuggestionData,
ISuggestionChange,
ISuggestionData,
ISuggestionsManager
ISuggestionsManager,
SuggestionType
} from './types';
import { User } from '@jupyterlab/services';

Expand Down Expand Up @@ -56,6 +57,7 @@ export abstract class BaseSuggestionsManager implements ISuggestionsManager {
notebook: NotebookPanel;
cell: Cell<ICellModel>;
author?: User.IIdentity | null;
type: SuggestionType;
}): Promise<string>;

abstract acceptSuggestion(options: {
Expand All @@ -77,8 +79,6 @@ export abstract class BaseSuggestionsManager implements ISuggestionsManager {
newSource: string;
}): Promise<void>;

protected _suggestionsMap = new Map<string, IAllSuggestionData>();

protected _notebookAdded(tracker: INotebookTracker, panel: NotebookPanel) {
panel.disposed.connect(p => {
const localPath = p.context.localPath;
Expand All @@ -88,6 +88,8 @@ export abstract class BaseSuggestionsManager implements ISuggestionsManager {
});
}

protected _suggestionsMap = new Map<string, IAllSuggestionData>();

protected _suggestionChanged = new Signal<
ISuggestionsManager,
ISuggestionChange
Expand Down
6 changes: 6 additions & 0 deletions packages/base/src/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import hintStr from '../style/icon/hint.svg';
import minimizeStr from '../style/icon/minimize.svg';
import expandStr from '../style/icon/expand.svg';
import collapseStr from '../style/icon/collapse.svg';
import locationStr from '../style/icon/location.svg';

export const hintIcon = new LabIcon({
name: 'jupyter-suggestions:hintIcon',
Expand All @@ -20,3 +21,8 @@ export const collapseIcon = new LabIcon({
name: 'jupyter-suggestions:collapseIcon',
svgstr: collapseStr
});

export const locationIcon = new LabIcon({
name: 'jupyter-suggestions:locationIcon',
svgstr: locationStr
});
9 changes: 5 additions & 4 deletions packages/base/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export * from './types';
export * from './suggestionsPanel';
export * from './baseSuggestionsManager';
export * from './icons';
export * from './tokens';
export * from './localSuggestionsManager';
export * from './baseSuggestionsManager';
export * from './registry';
export * from './suggestionCellMenu';
export * from './suggestionsPanel';
export * from './tokens';
export * from './tools';
export * from './types';
167 changes: 82 additions & 85 deletions packages/base/src/localSuggestionsManager/localSuggestionsManager.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
import {
Cell,
CellModel,
CodeCellModel,
ICellModel,
MarkdownCellModel,
RawCellModel
} from '@jupyterlab/cells';
import { Cell, ICellModel } from '@jupyterlab/cells';
import { ICell } from '@jupyterlab/nbformat';
import { NotebookPanel } from '@jupyterlab/notebook';
import { User } from '@jupyterlab/services';
import { UUID } from '@lumino/coreutils';

import { BaseSuggestionsManager } from '../baseSuggestionsManager';
import { cloneCellModel, deleteCellById } from '../tools';
import {
IAllSuggestionData,
IDict,
ISuggestionData,
ISuggestionMetadata,
ISuggestionsManager
ISuggestionsManager,
SuggestionType
} from '../types';
import { User } from '@jupyterlab/services';

export interface ISerializedSuggessionData {
originalCellId: string;
newSource: string;
metadata: ISuggestionMetadata;
type: SuggestionType;
}

const METADATA_KEY = 'jupyter_suggestion';
Expand Down Expand Up @@ -96,37 +92,50 @@ export class LocalSuggestionsManager
notebook: NotebookPanel;
cell: Cell<ICellModel>;
author?: User.IIdentity | null;
type: SuggestionType;
}): Promise<string> {
const { notebook, cell, author } = 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();
const suggestionContent: ISuggestionData = {
originalCellId: cellId,
cellModel: this._cloneCellModel(cell.model),
metadata: { author }
};
cellSuggesions[suggestionId] = suggestionContent;
await this._saveSuggestionToMetadata({
notebook,
cellId,
suggestionId,
suggestionContent
});
this._suggestionChanged.emit({
notebookPath: path,
cellId,
suggestionId,
operator: 'added'
});
switch (options.type) {
case SuggestionType.delete:
case SuggestionType.change: {
const { notebook, cell, author } = 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 suggestionContent: ISuggestionData = {
originalCellId: cellId,
cellModel: cloneCellModel(cell.model),
metadata: { author },
type: options.type
};
cellSuggesions[suggestionId] = suggestionContent;
await this._saveSuggestionToMetadata({
notebook,
cellId,
suggestionId,
suggestionContent
});
this._suggestionChanged.emit({
notebookPath: path,
cellId,
suggestionId,
operator: 'added'
});
break;
}

default:
break;
}

return suggestionId;
}

Expand All @@ -137,20 +146,38 @@ export class LocalSuggestionsManager
}): Promise<boolean> {
const { notebook, cellId, suggestionId } = options;
const notebookPath = notebook.context.localPath;

const currentSuggestion = await this.getSuggestion({
notebookPath,
cellId,
suggestionId
});
if (currentSuggestion && notebook.content.model?.cells) {
const newSource = currentSuggestion.cellModel.sharedModel.getSource();
for (const element of notebook.content.model.cells) {
if (element.id === cellId) {
element.sharedModel.setSource(newSource);
await this.deleteSuggestion(options);
return true;
switch (currentSuggestion.type) {
case SuggestionType.change: {
// In case of a change suggestion. the cell model is always defined
const newSource =
currentSuggestion.cellModel!.sharedModel.getSource();
for (const element of notebook.content.model.cells) {
if (element.id === cellId) {
element.sharedModel.setSource(newSource);
await this.deleteSuggestion(options);
return true;
}
}
break;
}
case SuggestionType.delete: {
const currentNotebook = notebook.content;
const { defaultCell } = currentNotebook.notebookConfig;
const deleted = await deleteCellById({
currentNotebook,
cellId,
defaultCell
});
return deleted;
}
default:
break;
}
}
return false;
Expand Down Expand Up @@ -219,12 +246,14 @@ export class LocalSuggestionsManager
suggestionContent: ISuggestionData;
}) {
const { notebook, cellId, suggestionId, suggestionContent } = options;
const { originalCellId, cellModel, metadata, type } = suggestionContent;
const currentSuggestions: IDict<IDict<ISerializedSuggessionData>> =
notebook.context.model.getMetadata(METADATA_KEY) ?? {};
const serializedData: ISerializedSuggessionData = {
originalCellId: suggestionContent.originalCellId,
newSource: suggestionContent.cellModel.sharedModel.getSource(),
metadata: suggestionContent.metadata
originalCellId,
newSource: cellModel?.sharedModel?.getSource() ?? '',
metadata: metadata,
type
};
const newData = {
...currentSuggestions,
Expand Down Expand Up @@ -288,50 +317,18 @@ export class LocalSuggestionsManager
await this._saveNotebook(notebook);
}

private _cloneCellModel(
cellModel: ICellModel,
newSource?: string
): ICellModel {
let copiedCellModel: CellModel | undefined;
const mimeType = cellModel.mimeType;
switch (cellModel.type) {
case 'code': {
copiedCellModel = new CodeCellModel();
break;
}
case 'markdown': {
copiedCellModel = new MarkdownCellModel();
break;
}
case 'raw': {
copiedCellModel = new RawCellModel();
break;
}
default:
break;
}

if (!copiedCellModel) {
throw new Error('Invalid cell type');
}
copiedCellModel.mimeType = mimeType;
copiedCellModel.sharedModel.setSource(
newSource ?? cellModel.sharedModel.getSource()
);
return copiedCellModel;
}

private _deserializedSuggestion(
serializedData: ISerializedSuggessionData,
cellMap: IDict<ICellModel>
): ISuggestionData {
const { originalCellId, newSource, metadata } = serializedData;
const { originalCellId, newSource, metadata, type } = serializedData;
const originalCellModel = cellMap[serializedData.originalCellId];
const newCellModel = this._cloneCellModel(originalCellModel, newSource);
const newCellModel = cloneCellModel(originalCellModel, newSource);
return {
originalCellId,
cellModel: newCellModel,
metadata
metadata,
type
};
}
}
Loading

0 comments on commit fdd757e

Please sign in to comment.