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

notebook image cleaning automation #159212

Merged
merged 44 commits into from Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
bc7c130
cache and cleaner complete, needs debounce
Aug 12, 2022
6d5a164
minor renaming and reformatting
Aug 12, 2022
4f8342e
Merge branch 'main' into mlively/cleaner
Aug 15, 2022
eef6539
Merge branch 'main' into mlively/cleaner
Aug 16, 2022
93b73a5
Merge branch 'main' into mlively/cleaner
Aug 16, 2022
5830c14
Merge branch 'main' into mlively/cleaner
Aug 16, 2022
a19f9f3
bugfix for paste into new cell
Aug 16, 2022
1385f69
Merge branch 'main' into mlively/cleaner
Aug 17, 2022
0d0cc73
cleaning functionality complete
Aug 19, 2022
6f892d0
Merge branch 'main' into mlively/cleaner
Aug 23, 2022
69c9430
Merge branch 'main' into mlively/cleaner
Aug 25, 2022
5296f31
refer to metadata as copy of current cell's
Aug 25, 2022
83a4ee4
Merge branch 'main' into mlively/cleaner
Aug 25, 2022
ca9ad19
check undef before reading from cache
Aug 25, 2022
635454b
Merge branch 'main' into mlively/cleaner
Aug 25, 2022
146d685
Merge branch 'main' into mlively/cleaner
Aug 29, 2022
742e98c
Merge branch 'main' into mlively/cleaner
Aug 29, 2022
2261aac
Merge branch 'main' into mlively/cleaner
Aug 30, 2022
9c441bd
working state, pending cache restructure
Aug 30, 2022
727aa9c
dots -> brackets
Aug 30, 2022
9aa082b
Merge branch 'main' into mlively/cleaner
Aug 30, 2022
09b615b
pre-class refactor
Aug 31, 2022
d08c452
massive cleaner refactor
Sep 1, 2022
05f532e
Merge branch 'main' into mlively/cleaner
Sep 1, 2022
26d9a67
cache typing, closed nb check, workspaceEdit only if metadata is changed
Sep 2, 2022
ba3752c
undefined access fix
Sep 2, 2022
d84c229
proper debouncer
rebornix Sep 7, 2022
dd8ee78
get it up to work again
rebornix Sep 7, 2022
87ae4c2
no need to loop
rebornix Sep 7, 2022
ccfbb6d
Merge remote-tracking branch 'origin/main' into mlively/cleaner
rebornix Sep 7, 2022
d30095f
cell metadata uri parsing regression
rebornix Sep 8, 2022
4f11fbb
diagnostic
rebornix Sep 8, 2022
d9d8e1a
Show diagnostics on document open
rebornix Sep 8, 2022
095ed18
transfer cache before file renames
rebornix Sep 8, 2022
2ed743d
disable word wrap in notebook diff editor
rebornix Sep 8, 2022
ea2add0
Avoid early notebook cell metadata deep clone
rebornix Sep 8, 2022
2e2bed9
No special case empty cell
rebornix Sep 8, 2022
ae3aeed
Merge branch 'main' into mlively/cleaner
rebornix Sep 8, 2022
29f995a
rename
rebornix Sep 8, 2022
fe00d03
better naming
rebornix Sep 8, 2022
24df26a
Merge branch 'main' into mlively/cleaner
rebornix Sep 8, 2022
34369f4
Quick fix for invalid image attachment
rebornix Sep 8, 2022
8cd3b08
cleanup
rebornix Sep 9, 2022
b106518
Add code action metadata
rebornix Sep 9, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions extensions/ipynb/.vscode/launch.json
@@ -0,0 +1,19 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"name": "Launch Extension",
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"request": "launch",
"type": "extensionHost"
}
]
}
60 changes: 34 additions & 26 deletions extensions/ipynb/package.json
Expand Up @@ -9,7 +9,7 @@
"vscode": "^1.57.0"
},
"enabledApiProposals": [
"documentPaste"
"documentPaste"
],
"activationEvents": [
"*"
Expand All @@ -27,21 +27,21 @@
}
},
"contributes": {
"configuration":[
{
"properties": {
"ipynb.experimental.pasteImages.enabled":{
"type": "boolean",
"scope": "resource",
"markdownDescription": "%ipynb.experimental.pasteImages.enabled%",
"default": false,
"tags": [
"experimental"
]
}
}
}
],
"configuration": [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space to tab

{
"properties": {
"ipynb.experimental.pasteImages.enabled": {
"type": "boolean",
"scope": "resource",
"markdownDescription": "%ipynb.experimental.pasteImages.enabled%",
"default": false,
"tags": [
"experimental"
]
}
}
}
],
"commands": [
{
"command": "ipynb.newUntitledIpynb",
Expand All @@ -52,6 +52,10 @@
{
"command": "ipynb.openIpynbInNotebookEditor",
"title": "Open ipynb file in notebook editor"
},
{
"command": "ipynb.cleanInvalidImageAttachment",
"title": "Clean invalid image attachment reference"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the word "reference" here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using reference for the image attachment link in the cell document, and use attachment for the data stored in the metadata.

}
],
"notebooks": [
Expand All @@ -66,16 +70,16 @@
"priority": "default"
}
],
"notebookRenderer": [
{
"id": "vscode.markdown-it-cell-attachment-renderer",
"displayName": "Markdown it ipynb Cell Attachment renderer",
"entrypoint": {
"extends": "vscode.markdown-it-renderer",
"path": "./notebook-out/cellAttachmentRenderer.js"
}
}
],
"notebookRenderer": [
{
"id": "vscode.markdown-it-cell-attachment-renderer",
"displayName": "Markdown it ipynb Cell Attachment renderer",
"entrypoint": {
"extends": "vscode.markdown-it-renderer",
"path": "./notebook-out/cellAttachmentRenderer.js"
}
}
],
"menus": {
"file/newFile": [
{
Expand All @@ -90,6 +94,10 @@
{
"command": "ipynb.openIpynbInNotebookEditor",
"when": "false"
},
{
"command": "ipynb.cleanInvalidImageAttachment",
"when": "false"
}
]
}
Expand Down
5 changes: 5 additions & 0 deletions extensions/ipynb/src/constants.ts
Expand Up @@ -3,4 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';

export const defaultNotebookFormat = { major: 4, minor: 2 };
export const ATTACHMENT_CLEANUP_COMMANDID = 'ipynb.cleanInvalidImageAttachment';
rebornix marked this conversation as resolved.
Show resolved Hide resolved

export const JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR: vscode.DocumentSelector = { notebookType: 'jupyter-notebook', language: 'markdown' };
137 changes: 137 additions & 0 deletions extensions/ipynb/src/helper.ts
@@ -0,0 +1,137 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

export function deepClone<T>(obj: T): T {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have more generally available versions of these functions? (and better tested?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amunger currently we don't, I borrowed this from the core.

if (!obj || typeof obj !== 'object') {
return obj;
}
if (obj instanceof RegExp) {
// See https://github.com/microsoft/TypeScript/issues/10990
return obj as any;
}
const result: any = Array.isArray(obj) ? [] : {};
Object.keys(<any>obj).forEach((key: string) => {
if ((<any>obj)[key] && typeof (<any>obj)[key] === 'object') {
result[key] = deepClone((<any>obj)[key]);
} else {
result[key] = (<any>obj)[key];
}
});
return result;
}

// from https://github.com/microsoft/vscode/blob/43ae27a30e7b5e8711bf6b218ee39872ed2b8ef6/src/vs/base/common/objects.ts#L117
export function objectEquals(one: any, other: any) {
if (one === other) {
return true;
}
if (one === null || one === undefined || other === null || other === undefined) {
return false;
}
if (typeof one !== typeof other) {
return false;
}
if (typeof one !== 'object') {
return false;
}
if ((Array.isArray(one)) !== (Array.isArray(other))) {
return false;
}

let i: number;
let key: string;

if (Array.isArray(one)) {
if (one.length !== other.length) {
return false;
}
for (i = 0; i < one.length; i++) {
if (!objectEquals(one[i], other[i])) {
return false;
}
}
} else {
const oneKeys: string[] = [];

for (key in one) {
oneKeys.push(key);
}
oneKeys.sort();
const otherKeys: string[] = [];
for (key in other) {
otherKeys.push(key);
}
otherKeys.sort();
if (!objectEquals(oneKeys, otherKeys)) {
return false;
}
for (i = 0; i < oneKeys.length; i++) {
if (!objectEquals(one[oneKeys[i]], other[oneKeys[i]])) {
return false;
}
}
}

return true;
}

interface Options<T> {
callback: (value: T) => void;

merge?: (input: T[]) => T;
delay?: number;
}


export class DebounceTrigger<T> {

private _isPaused = 0;
protected _queue: T[] = [];
private _callbackFn: (value: T) => void;
private _mergeFn?: (input: T[]) => T;
private readonly _delay: number;
private _handle: any | undefined;

constructor(options: Options<T>) {
this._callbackFn = options.callback;
this._mergeFn = options.merge;
this._delay = options.delay ?? 100;
}

private pause(): void {
this._isPaused++;
}

private resume(): void {
if (this._isPaused !== 0 && --this._isPaused === 0) {
if (this._mergeFn) {
const items = Array.from(this._queue);
this._queue = [];
this._callbackFn(this._mergeFn(items));

} else {
while (!this._isPaused && this._queue.length !== 0) {
this._callbackFn(this._queue.shift()!);
}
}
}
}

trigger(item: T): void {
if (!this._handle) {
this.pause();
this._handle = setTimeout(() => {
this._handle = undefined;
this.resume();
}, this._delay);
}

if (this._isPaused !== 0) {
this._queue.push(item);
} else {
this._callbackFn(item);
}
}
}
13 changes: 10 additions & 3 deletions extensions/ipynb/src/ipynbMain.ts
Expand Up @@ -4,9 +4,10 @@
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import { ensureAllNewCellsHaveCellIds } from './cellIdService';
import { NotebookSerializer } from './notebookSerializer';
import * as NotebookImagePaste from './notebookImagePaste';
import { ensureAllNewCellsHaveCellIds } from './cellIdService';
import { notebookImagePasteSetup } from './notebookImagePaste';
import { AttachmentCleaner } from './notebookAttachmentCleaner';

// From {nbformat.INotebookMetadata} in @jupyterlab/coreutils
type NotebookMetadata = {
Expand Down Expand Up @@ -78,7 +79,13 @@ export function activate(context: vscode.ExtensionContext) {
await vscode.window.showNotebookDocument(document);
}));

context.subscriptions.push(NotebookImagePaste.imagePasteSetup());
context.subscriptions.push(notebookImagePasteSetup());

const enabled = vscode.workspace.getConfiguration('ipynb').get('experimental.pasteImages.enabled', false);
if (enabled) {
const cleaner = new AttachmentCleaner();
context.subscriptions.push(cleaner);
}

// Update new file contribution
vscode.extensions.onDidChange(() => {
Expand Down