diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 129326be681..44be5424821 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -13,7 +13,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { readonly } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { INotebookDisplayOrder, parseCellUri, parseCellHandle, ITransformedDisplayOutputDto, IOrderedMimeType, IStreamOutput, IErrorOutput, mimeTypeSupportedByCore, IOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookDisplayOrder, parseCellUri, parseCellHandle, ITransformedDisplayOutputDto, IOrderedMimeType, IStreamOutput, IErrorOutput, mimeTypeSupportedByCore, IOutput, sortMimeTypes, diff } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ISplice } from 'vs/base/common/sequence'; interface ExtHostOutputDisplayOrder { @@ -21,67 +21,6 @@ interface ExtHostOutputDisplayOrder { userOrder?: glob.ParsedPattern[]; } -interface IMutableSplice extends ISplice { - deleteCount: number; -} - -function diff(before: T[], after: T[], contains: (a: T) => boolean): ISplice[] { - const result: IMutableSplice[] = []; - - function pushSplice(start: number, deleteCount: number, toInsert: T[]): void { - if (deleteCount === 0 && toInsert.length === 0) { - return; - } - - const latest = result[result.length - 1]; - - if (latest && latest.start + latest.deleteCount === start) { - latest.deleteCount += deleteCount; - latest.toInsert.push(...toInsert); - } else { - result.push({ start, deleteCount, toInsert }); - } - } - - let beforeIdx = 0; - let afterIdx = 0; - - while (true) { - if (beforeIdx === before.length) { - pushSplice(beforeIdx, 0, after.slice(afterIdx)); - break; - } - - if (afterIdx === after.length) { - pushSplice(beforeIdx, before.length - beforeIdx, []); - break; - } - - const beforeElement = before[beforeIdx]; - const afterElement = after[afterIdx]; - - if (beforeElement === afterElement) { - // equal - beforeIdx += 1; - afterIdx += 1; - continue; - } - - if (contains(afterElement)) { - // `afterElement` exists before, which means some elements before `afterElement` are deleted - pushSplice(beforeIdx, 1, []); - beforeIdx += 1; - } else { - // `afterElement` added - pushSplice(beforeIdx, 0, [afterElement]); - afterIdx += 1; - } - } - - return result; -} - - export class ExtHostCell implements vscode.NotebookCell { private static _handlePool: number = 0; @@ -330,9 +269,8 @@ export class ExtHostNotebookDocument implements vscode.NotebookDocument, vscode. let mimeTypes = Object.keys(output.data); // TODO@rebornix, the document display order might be assigned a bit later. We need to postpone sending the outputs to the core side. - const sorted = mimeTypes.sort((a, b) => { - return this.getMimeTypeOrder(a) - this.getMimeTypeOrder(b); - }); + let coreDisplayOrder = this.renderingHandler.outputDisplayOrder; + const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], this._parsedDisplayOrder, coreDisplayOrder?.defaultOrder || []); let orderMimeTypes: IOrderedMimeType[] = []; @@ -380,62 +318,6 @@ export class ExtHostNotebookDocument implements vscode.NotebookDocument, vscode. }; } - findRichestMimeType(output: vscode.CellOutput) { - if (output.outputKind === CellOutputKind.Rich) { - let mimeTypes = Object.keys(output.data); - - const sorted = mimeTypes.sort((a, b) => { - return this.getMimeTypeOrder(a) - this.getMimeTypeOrder(b); - }); - - if (sorted.length) { - return sorted[0]; - } - } - - return undefined; - } - - getMimeTypeOrder(mimeType: string) { - let order = 0; - let coreDisplayOrder = this.renderingHandler.outputDisplayOrder; - if (coreDisplayOrder) { - // User order has highest priority - let userDisplayOrder = coreDisplayOrder.userOrder; - - if (userDisplayOrder) { - for (let i = 0; i < userDisplayOrder.length; i++) { - if (userDisplayOrder[i](mimeType)) { - return order; - } - order++; - } - } - - let documentDisplayOrder = this._parsedDisplayOrder; - - for (let i = 0; i < documentDisplayOrder.length; i++) { - if (documentDisplayOrder[i](mimeType)) { - return order; - } - - order++; - } - - let defaultOrder = coreDisplayOrder.defaultOrder; - - for (let i = 0; i < defaultOrder.length; i++) { - if (defaultOrder[i](mimeType)) { - return order; - } - - order++; - } - } - - return order; - } - getCell(cellHandle: number) { return this.cells.find(cell => cell.handle === cellHandle); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 03b65386be1..3999ed32d57 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as glob from 'vs/base/common/glob'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IRelativePattern } from 'vs/base/common/glob'; import { PieceTreeTextBufferFactory } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ISplice } from 'vs/base/common/sequence'; export enum CellKind { Markdown = 1, @@ -27,15 +28,14 @@ export const NOTEBOOK_DISPLAY_ORDER = [ 'text/html', 'image/svg+xml', 'text/markdown', - 'image/svg+xml', 'image/png', 'image/jpeg', 'text/plain' ]; export interface INotebookDisplayOrder { - defaultOrder: (string | IRelativePattern)[]; - userOrder?: (string | IRelativePattern)[]; + defaultOrder: (string | glob.IRelativePattern)[]; + userOrder?: (string | glob.IRelativePattern)[]; } export interface INotebookMimeTypeSelector { @@ -214,3 +214,98 @@ export function mimeTypeSupportedByCore(mimeType: string) { } +function getMimeTypeOrder(mimeType: string, userDisplayOrder: glob.ParsedPattern[], documentDisplayOrder: glob.ParsedPattern[], defaultOrder: glob.ParsedPattern[]) { + let order = 0; + for (let i = 0; i < userDisplayOrder.length; i++) { + if (userDisplayOrder[i](mimeType)) { + return order; + } + order++; + } + + for (let i = 0; i < documentDisplayOrder.length; i++) { + if (documentDisplayOrder[i](mimeType)) { + return order; + } + + order++; + } + + for (let i = 0; i < defaultOrder.length; i++) { + if (defaultOrder[i](mimeType)) { + return order; + } + + order++; + } + + return order; +} + +export function sortMimeTypes(mimeTypes: string[], userDisplayOrder: glob.ParsedPattern[], documentDisplayOrder: glob.ParsedPattern[], defaultOrder: glob.ParsedPattern[]) { + const sorted = mimeTypes.sort((a, b) => { + return getMimeTypeOrder(a, userDisplayOrder, documentDisplayOrder, defaultOrder) - getMimeTypeOrder(b, userDisplayOrder, documentDisplayOrder, defaultOrder); + }); + + return sorted; +} + +interface IMutableSplice extends ISplice { + deleteCount: number; +} + +export function diff(before: T[], after: T[], contains: (a: T) => boolean): ISplice[] { + const result: IMutableSplice[] = []; + + function pushSplice(start: number, deleteCount: number, toInsert: T[]): void { + if (deleteCount === 0 && toInsert.length === 0) { + return; + } + + const latest = result[result.length - 1]; + + if (latest && latest.start + latest.deleteCount === start) { + latest.deleteCount += deleteCount; + latest.toInsert.push(...toInsert); + } else { + result.push({ start, deleteCount, toInsert }); + } + } + + let beforeIdx = 0; + let afterIdx = 0; + + while (true) { + if (beforeIdx === before.length) { + pushSplice(beforeIdx, 0, after.slice(afterIdx)); + break; + } + + if (afterIdx === after.length) { + pushSplice(beforeIdx, before.length - beforeIdx, []); + break; + } + + const beforeElement = before[beforeIdx]; + const afterElement = after[afterIdx]; + + if (beforeElement === afterElement) { + // equal + beforeIdx += 1; + afterIdx += 1; + continue; + } + + if (contains(afterElement)) { + // `afterElement` exists before, which means some elements before `afterElement` are deleted + pushSplice(beforeIdx, 1, []); + beforeIdx += 1; + } else { + // `afterElement` added + pushSplice(beforeIdx, 0, [afterElement]); + afterIdx += 1; + } + } + + return result; +} diff --git a/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts new file mode 100644 index 00000000000..831a22fa587 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts @@ -0,0 +1,257 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as glob from 'vs/base/common/glob'; +import { NOTEBOOK_DISPLAY_ORDER, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +suite('NotebookCommon', () => { + test('sortMimeTypes default orders', function () { + const defaultDisplayOrder = NOTEBOOK_DISPLAY_ORDER.map(pattern => glob.parse(pattern)); + + assert.deepEqual(sortMimeTypes( + [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' + ], [], [], defaultDisplayOrder), + [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' + ] + ); + + assert.deepEqual(sortMimeTypes( + [ + 'application/json', + 'text/markdown', + 'application/javascript', + 'text/html', + 'text/plain', + 'image/png', + 'image/jpeg', + 'image/svg+xml' + ], [], [], defaultDisplayOrder), + [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' + ] + ); + + assert.deepEqual(sortMimeTypes( + [ + 'text/markdown', + 'application/json', + 'text/plain', + 'image/jpeg', + 'application/javascript', + 'text/html', + 'image/png', + 'image/svg+xml' + ], [], [], defaultDisplayOrder), + [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' + ] + ); + }); + + test('sortMimeTypes document orders', function () { + const defaultDisplayOrder = NOTEBOOK_DISPLAY_ORDER.map(pattern => glob.parse(pattern)); + assert.deepEqual(sortMimeTypes( + [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' + ], [], [ + 'text/markdown', + 'text/html', + 'application/json' + ].map(pattern => glob.parse(pattern)), defaultDisplayOrder), + [ + 'text/markdown', + 'text/html', + 'application/json', + 'application/javascript', + 'image/svg+xml', + 'image/png', + 'image/jpeg', + 'text/plain' + ] + ); + + assert.deepEqual(sortMimeTypes( + [ + 'text/markdown', + 'application/json', + 'text/plain', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'image/jpeg', + 'image/png' + ], [], [ + 'text/html', + 'text/markdown', + 'application/json' + ].map(pattern => glob.parse(pattern)), defaultDisplayOrder), + [ + 'text/html', + 'text/markdown', + 'application/json', + 'application/javascript', + 'image/svg+xml', + 'image/png', + 'image/jpeg', + 'text/plain' + ] + ); + }); + + test('sortMimeTypes user orders', function () { + const defaultDisplayOrder = NOTEBOOK_DISPLAY_ORDER.map(pattern => glob.parse(pattern)); + assert.deepEqual(sortMimeTypes( + [ + 'application/json', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'text/markdown', + 'image/png', + 'image/jpeg', + 'text/plain' + ], [ + 'image/png', + 'text/plain', + ].map(pattern => glob.parse(pattern)), + [ + 'text/markdown', + 'text/html', + 'application/json' + ].map(pattern => glob.parse(pattern)), defaultDisplayOrder), + [ + 'image/png', + 'text/plain', + 'text/markdown', + 'text/html', + 'application/json', + 'application/javascript', + 'image/svg+xml', + 'image/jpeg', + ] + ); + + assert.deepEqual(sortMimeTypes( + [ + 'text/markdown', + 'application/json', + 'text/plain', + 'application/javascript', + 'text/html', + 'image/svg+xml', + 'image/jpeg', + 'image/png' + ], [ + 'application/json', + 'text/html', + ].map(pattern => glob.parse(pattern)), + [ + 'text/html', + 'text/markdown', + 'application/json' + ].map(pattern => glob.parse(pattern)), defaultDisplayOrder), + [ + 'application/json', + 'text/html', + 'text/markdown', + 'application/javascript', + 'image/svg+xml', + 'image/png', + 'image/jpeg', + 'text/plain' + ] + ); + }); + + test('sortMimeTypes glob', function () { + const defaultDisplayOrder = NOTEBOOK_DISPLAY_ORDER.map(pattern => glob.parse(pattern)); + + // unknown mime types come last + assert.deepEqual(sortMimeTypes( + [ + 'application/json', + 'application/vnd-vega.json', + 'application/vnd-plot.json', + 'application/javascript', + 'text/html' + ], [].map(pattern => glob.parse(pattern)), + [ + 'text/markdown', + 'text/html', + 'application/json' + ].map(pattern => glob.parse(pattern)), defaultDisplayOrder), + [ + 'text/html', + 'application/json', + 'application/javascript', + 'application/vnd-vega.json', + 'application/vnd-plot.json' + ], + 'unknown mimetypes keep the ordering' + ); + + assert.deepEqual(sortMimeTypes( + [ + 'application/json', + 'application/javascript', + 'text/html', + 'application/vnd-plot.json', + 'application/vnd-vega.json' + ], [].map(pattern => glob.parse(pattern)), + [ + 'application/vnd-vega*', + 'text/markdown', + 'text/html', + 'application/json' + ].map(pattern => glob.parse(pattern)), defaultDisplayOrder), + [ + 'application/vnd-vega.json', + 'text/html', + 'application/json', + 'application/javascript', + 'application/vnd-plot.json' + ], + 'glob *' + ); + }); +});