Merge branch 'notebook/dev' into main

This commit is contained in:
rebornix 2021-03-19 14:00:42 -07:00
commit 0eff78fc42
No known key found for this signature in database
GPG key ID: 181FC90D15393C20
25 changed files with 685 additions and 249 deletions

View file

@ -140,6 +140,7 @@ export class MenuId {
static readonly CommentThreadActions = new MenuId('CommentThreadActions');
static readonly CommentTitle = new MenuId('CommentTitle');
static readonly CommentActions = new MenuId('CommentActions');
static readonly NotebookToolbar = new MenuId('NotebookToolbar');
static readonly NotebookCellTitle = new MenuId('NotebookCellTitle');
static readonly NotebookCellInsert = new MenuId('NotebookCellInsert');
static readonly NotebookCellBetween = new MenuId('NotebookCellBetween');

View file

@ -1402,8 +1402,25 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/106744, NotebookSerializer
export interface NotebookSerializer {
dataToNotebook(data: Uint8Array): NotebookData | Thenable<NotebookData>;
notebookToData(data: NotebookData): Uint8Array | Thenable<Uint8Array>;
}
export namespace notebook {
// TODO@api use NotebookDocumentFilter instead of just notebookType:string?
// TODO@API options duplicates the more powerful variant on NotebookContentProvider
export function registerNotebookSerializer(notebookType: string, provider: NotebookSerializer, options?: NotebookDocumentContentOptions): Disposable;
}
//#endregion
//#region https://github.com/microsoft/vscode/issues/106744, NotebookContentProvider
interface NotebookDocumentBackup {
/**
* Unique identifier for the backup.

View file

@ -24,7 +24,7 @@ import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/no
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService';
import { ICellEditOperation, ICellRange, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellEditOperation, ICellRange, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, NotebookDataDto, TransientMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@ -107,6 +107,7 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
private readonly _proxy: ExtHostNotebookShape;
private readonly _notebookProviders = new Map<string, { controller: IMainNotebookController, disposable: IDisposable }>();
private readonly _notebookSerializer = new Map<number, IDisposable>();
private readonly _notebookKernelProviders = new Map<number, { extension: NotebookExtensionDescription, emitter: Emitter<URI | undefined>, provider: IDisposable }>();
private readonly _editorEventListenersMapping = new Map<string, DisposableStore>();
private readonly _documentEventListenersMapping = new ResourceMap<DisposableStore>();
@ -124,7 +125,7 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
@IEditorService private readonly _editorService: IEditorService,
@ILogService private readonly _logService: ILogService,
@INotebookCellStatusBarService private readonly _cellStatusBarService: INotebookCellStatusBarService,
@INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService,
@INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService,
@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
@ -147,6 +148,7 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
item.emitter.dispose();
item.provider.dispose();
}
dispose(this._notebookSerializer.values());
dispose(this._editorEventListenersMapping.values());
dispose(this._documentEventListenersMapping.values());
dispose(this._cellStatusBarEntries.values());
@ -323,7 +325,7 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
this._proxy.$acceptNotebookActiveKernelChange(e);
}));
this._disposables.add(this._notebookService.onNotebookDocumentSaved(e => {
this._disposables.add(this._notebookEditorModelResolverService.onDidSaveNotebook(e => {
this._proxy.$acceptModelSaved(e);
}));
@ -403,8 +405,8 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
contentOptions.transientOutputs = newOptions.transientOutputs;
},
viewOptions: options.viewOptions,
openNotebook: async (viewType: string, uri: URI, backupId: string | undefined, token: CancellationToken, untitledDocumentData?: VSBuffer) => {
const data = await this._proxy.$openNotebook(viewType, uri, backupId, token, untitledDocumentData);
open: async (uri: URI, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken) => {
const data = await this._proxy.$openNotebook(viewType, uri, backupId, untitledDocumentData, token);
return {
data,
transientOptions: contentOptions
@ -452,6 +454,24 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
}
}
$registerNotebookSerializer(handle: number, extension: NotebookExtensionDescription, viewType: string, options: TransientOptions): void {
const registration = this._notebookService.registerNotebookSerializer(viewType, extension, {
options,
dataToNotebook: (data: VSBuffer): Promise<NotebookDataDto> => {
return this._proxy.$dataToNotebook(handle, data);
},
notebookToData: (data: NotebookDataDto): Promise<VSBuffer> => {
return this._proxy.$notebookToData(handle, data);
}
});
this._notebookSerializer.set(handle, registration);
}
$unregisterNotebookSerializer(handle: number): void {
this._notebookSerializer.get(handle)?.dispose();
this._notebookSerializer.delete(handle);
}
async $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void> {
const emitter = new Emitter<URI | undefined>();
const that = this;
@ -585,7 +605,7 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
async $tryOpenDocument(uriComponents: UriComponents): Promise<URI> {
const uri = URI.revive(uriComponents);
const ref = await this._notebookModelResolverService.resolve(uri, undefined);
const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined);
this._modelReferenceCollection.add(uri, ref);
return uri;
}
@ -593,7 +613,7 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
async $trySaveDocument(uriComponents: UriComponents) {
const uri = URI.revive(uriComponents);
const ref = await this._notebookModelResolverService.resolve(uri);
const ref = await this._notebookEditorModelResolverService.resolve(uri);
const saveResult = await ref.object.save();
ref.dispose();
return saveResult;

View file

@ -1051,6 +1051,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeActiveNotebookKernel;
},
registerNotebookSerializer(viewType, serializer, options) {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookSerializer(extension, viewType, serializer, options);
},
registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider, options?: {
transientOutputs: boolean;
transientMetadata: { [K in keyof vscode.NotebookCellMetadata]?: boolean }

View file

@ -50,7 +50,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, ProvidedPortAttributes } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { Dto } from 'vs/base/common/types';
import { DebugConfigurationProviderTriggerKind, WorkspaceTrustState } from 'vs/workbench/api/common/extHostTypes';
@ -840,6 +840,10 @@ export interface MainThreadNotebookShape extends IDisposable {
}): Promise<void>;
$updateNotebookProviderOptions(viewType: string, options?: { transientOutputs: boolean; transientMetadata: TransientMetadata; }): Promise<void>;
$unregisterNotebookProvider(viewType: string): Promise<void>;
$registerNotebookSerializer(handle: number, extension: NotebookExtensionDescription, viewType: string, options: TransientOptions): void;
$unregisterNotebookSerializer(handle: number): void;
$registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void>;
$unregisterNotebookKernelProvider(handle: number): Promise<void>;
$onNotebookKernelChange(handle: number, uri: UriComponents | undefined): void;
@ -1863,10 +1867,15 @@ export interface ExtHostNotebookShape {
$executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void>;
$cancelNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void>;
$onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void;
$openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, token: CancellationToken, untitledDocumentData?: VSBuffer): Promise<NotebookDataDto>;
$openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<NotebookDataDto>;
$saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>;
$saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>;
$backupNotebook(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string>;
$dataToNotebook(handle: number, data: VSBuffer): Promise<NotebookDataDto>;
$notebookToData(handle: number, data: NotebookDataDto): Promise<VSBuffer>;
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void;
$acceptDirtyStateChanged(uriComponents: UriComponents, isDirty: boolean): void;
$acceptModelSaved(uriComponents: UriComponents): void;

View file

@ -5,7 +5,7 @@
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import * as UUID from 'vs/base/common/uuid';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
@ -17,7 +17,7 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
import { CellStatusbarAlignment, CellUri, INotebookCellStatusBarEntry, INotebookExclusiveDocumentFilter, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellStatusbarAlignment, CellUri, INotebookCellStatusBarEntry, INotebookExclusiveDocumentFilter, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import * as vscode from 'vscode';
import { ResourceMap } from 'vs/base/common/map';
import { ExtHostCell, ExtHostNotebookDocument } from './extHostNotebookDocument';
@ -209,6 +209,7 @@ export class NotebookEditorDecorationType {
}
}
type NotebookContentProviderData = {
readonly provider: vscode.NotebookContentProvider;
readonly extension: IExtensionDescription;
@ -496,9 +497,52 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
});
}
// --- serialize/deserialize
private _handlePool = 0;
private readonly _notebookSerializer = new Map<number, vscode.NotebookSerializer>();
registerNotebookSerializer(extension: IExtensionDescription, viewType: string, serializer: vscode.NotebookSerializer, options?: TransientOptions): vscode.Disposable {
const handle = this._handlePool++;
this._notebookSerializer.set(handle, serializer);
this._proxy.$registerNotebookSerializer(
handle,
{ id: extension.identifier, location: extension.extensionLocation, description: extension.description },
viewType,
options ?? { transientOutputs: false, transientMetadata: {} }
);
return toDisposable(() => {
this._proxy.$unregisterNotebookSerializer(handle);
});
}
async $dataToNotebook(handle: number, bytes: VSBuffer): Promise<NotebookDataDto> {
const serializer = this._notebookSerializer.get(handle);
if (!serializer) {
throw new Error('NO serializer found');
}
const data = await serializer.dataToNotebook(bytes.buffer);
return {
metadata: typeConverters.NotebookDocumentMetadata.from(data.metadata),
cells: data.cells.map(typeConverters.NotebookCellData.from),
};
}
async $notebookToData(handle: number, data: NotebookDataDto): Promise<VSBuffer> {
const serializer = this._notebookSerializer.get(handle);
if (!serializer) {
throw new Error('NO serializer found');
}
const bytes = await serializer.notebookToData({
metadata: typeConverters.NotebookDocumentMetadata.to(data.metadata),
cells: data.cells.map(typeConverters.NotebookCellData.to)
});
return VSBuffer.wrap(bytes);
}
// --- open, save, saveAs, backup
async $openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, token: CancellationToken, untitledDocumentData?: VSBuffer): Promise<NotebookDataDto> {
async $openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<NotebookDataDto> {
const { provider } = this._getProviderData(viewType);
const data = await provider.openNotebook(URI.revive(uri), { backupId, untitledDocumentData: untitledDocumentData?.buffer }, token);
return {

View file

@ -1469,6 +1469,16 @@ export namespace NotebookCellData {
outputs: data.outputs ? data.outputs.map(NotebookCellOutput.from) : []
};
}
export function to(data: notebooks.ICellDto2): vscode.NotebookCellData {
return new types.NotebookCellData(
NotebookCellKind.to(data.cellKind),
data.source,
data.language,
data.outputs ? data.outputs.map(NotebookCellOutput.to) : undefined,
data.metadata ? NotebookCellMetadata.to(data.metadata) : undefined,
);
}
}
export namespace NotebookCellOutputItem {

View file

@ -170,6 +170,12 @@ const apiMenus: IAPIMenu[] = [
description: localize('comment.actions', "The contributed comment context menu, rendered as buttons below the comment editor"),
supportsSubmenus: false
},
{
key: 'notebook/toolbar',
id: MenuId.NotebookToolbar,
description: localize('notebook.toolbar', "The contributed notebook toolbar menu"),
proposed: true
},
{
key: 'notebook/cell/title',
id: MenuId.NotebookCellTitle,

View file

@ -5,7 +5,8 @@
// Scrollable Element
export const SCROLLABLE_ELEMENT_PADDING_TOP = 22;
export const SCROLLABLE_ELEMENT_PADDING_TOP = 20;
// export const SCROLLABLE_ELEMENT_PADDING_TOP_WITH_TOOLBAR = 8;
// Cell sizing related
export const CELL_MARGIN = 8;

View file

@ -173,6 +173,12 @@ registerAction2(class extends NotebookCellAction {
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow,
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
weight: KeybindingWeight.WorkbenchContrib
},
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE),
group: CellOverflowToolbarGroups.Edit,
order: 12
}
});
}

View file

@ -610,7 +610,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorContext, {
when: NOTEBOOK_EDITOR_FOCUSED
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
command: {
id: EXECUTE_NOTEBOOK_COMMAND_ID,
title: localize('notebookActions.menu.executeNotebook', "Execute Notebook (Run all cells)"),
@ -618,10 +618,10 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
},
order: -1,
group: 'navigation',
when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.toNegated(), executeNotebookCondition)
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.toNegated(), executeNotebookCondition)
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
command: {
id: CANCEL_NOTEBOOK_COMMAND_ID,
title: localize('notebookActions.menu.cancelNotebook', "Stop Notebook Execution"),
@ -629,7 +629,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
},
order: -1,
group: 'navigation',
when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK)
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK)
});
registerAction2(class extends NotebookCellAction {
@ -1494,8 +1494,8 @@ registerAction2(class extends NotebookAction {
id: CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID,
title: localize('clearAllCellsOutputs', 'Clear All Cells Outputs'),
menu: {
id: MenuId.EditorTitle,
when: NOTEBOOK_IS_ACTIVE_EDITOR,
id: MenuId.NotebookToolbar,
// when: NOTEBOOK_IS_ACTIVE_EDITOR,
group: 'navigation',
order: 0
},

View file

@ -25,14 +25,11 @@ import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser';
import { CellEditState, ICellOutputViewModel, IDisplayOutputLayoutUpdateRequest, IGenericCellViewModel, IInsetRenderOutput, NotebookLayoutInfo, NOTEBOOK_DIFF_EDITOR_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { DiffSide, DIFF_CELL_MARGIN, IDiffCellInfo, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser';
import { Emitter } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { CellUri, INotebookDiffEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { FileService } from 'vs/platform/files/common/fileService';
import { IFileService } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IDiffChange } from 'vs/base/common/diff/diff';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
@ -91,8 +88,6 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
@IContextKeyService readonly contextKeyService: IContextKeyService,
@INotebookEditorWorkerService readonly notebookEditorWorkerService: INotebookEditorWorkerService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IFileService private readonly _fileService: FileService,
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService storageService: IStorageService,
) {
@ -304,42 +299,9 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
this._revealFirst = true;
this._modifiedResourceDisposableStore.add(this._fileService.watch(this._model.modified.resource));
this._modifiedResourceDisposableStore.add(this._fileService.onDidFilesChange(async e => {
if (this._model === null) {
return;
}
if (e.contains(this._model.modified.resource)) {
if (this._model.modified.isDirty()) {
return;
}
const modified = this._model.modified;
const lastResolvedFileStat = modified.lastResolvedFileStat;
const currFileStat = await this._resolveStats(modified.resource);
if (lastResolvedFileStat && currFileStat && currFileStat.mtime > lastResolvedFileStat.mtime) {
await this._model.resolveModifiedFromDisk();
await this.updateLayout();
return;
}
}
if (e.contains(this._model.original.resource)) {
if (this._model.original.isDirty()) {
return;
}
const original = this._model.original;
const lastResolvedFileStat = original.lastResolvedFileStat;
const currFileStat = await this._resolveStats(original.resource);
if (lastResolvedFileStat && currFileStat && currFileStat.mtime > lastResolvedFileStat.mtime) {
await this._model.resolveOriginalFromDisk();
await this.updateLayout();
return;
}
this._modifiedResourceDisposableStore.add(Event.any(this._model.original.notebook.onDidChangeContent, this._model.modified.notebook.onDidChangeContent)(e => {
if (this._model !== null) {
this.updateLayout();
}
}));
@ -419,19 +381,6 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
this._originalWebview.element.style.left = `16px`;
}
private async _resolveStats(resource: URI) {
if (resource.scheme === Schemas.untitled) {
return undefined;
}
try {
const newStats = await this._fileService.resolve(resource, { resolveMetadata: true });
return newStats;
} catch (e) {
return undefined;
}
}
async updateLayout() {
if (!this._model) {
return;

View file

@ -11,6 +11,25 @@
position: relative;
}
.monaco-workbench .notebookOverlay .notebook-top-toolbar {
width: 100%;
display: inline-flex;
padding-left: 8px;
margin-top: 4px;
}
.monaco-workbench .notebookOverlay .notebook-top-toolbar .monaco-action-bar .action-item {
width: 24px;
height: 22px;
display: flex;
align-items: center;
}
.monaco-workbench .notebookOverlay .notebook-top-toolbar .monaco-action-bar .action-item .action-label {
background-size: 16px;
margin: 4px 4px 0 4px;
}
.monaco-workbench .cell.markdown {
user-select: text;
-webkit-user-select: text;
@ -855,7 +874,7 @@
.monaco-workbench .notebookOverlay > .cell-list-container .notebook-folding-indicator .codicon {
visibility: visible;
height: 16px;
padding: 4px;
padding: 4px 4px 4px 6px;
}
/** Theming */

View file

@ -34,11 +34,11 @@ class NotebookDiffEditorModel extends EditorModel implements INotebookDiffEditor
}
async resolveOriginalFromDisk() {
await this.original.load({ forceReadFromDisk: true });
await this.original.load({ forceReadFromFile: true });
}
async resolveModifiedFromDisk() {
await this.modified.load({ forceReadFromDisk: true });
await this.modified.load({ forceReadFromFile: true });
}
dispose(): void {
@ -53,7 +53,7 @@ export class NotebookDiffEditorInput extends EditorInput {
static readonly ID: string = 'workbench.input.diffNotebookInput';
private _textModel: IReference<IResolvedNotebookEditorModel> | null = null;
private _modifiedTextModel: IReference<IResolvedNotebookEditorModel> | null = null;
private _originalTextModel: IReference<IResolvedNotebookEditorModel> | null = null;
private _defaultDirtyState: boolean = false;
@ -82,14 +82,14 @@ export class NotebookDiffEditorInput extends EditorInput {
}
isDirty() {
if (!this._textModel) {
return !!this._defaultDirtyState;
if (!this._modifiedTextModel) {
return this._defaultDirtyState;
}
return this._textModel.object.isDirty();
return this._modifiedTextModel.object.isDirty();
}
isUntitled(): boolean {
return this._textModel?.object.isUntitled() || false;
return this._modifiedTextModel?.object.isUntitled() || false;
}
isReadonly() {
@ -97,12 +97,12 @@ export class NotebookDiffEditorInput extends EditorInput {
}
async save(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
if (this._textModel) {
if (this._modifiedTextModel) {
if (this.isUntitled()) {
return this.saveAs(group, options);
} else {
await this._textModel.object.save();
await this._modifiedTextModel.object.save();
}
return this;
@ -112,7 +112,7 @@ export class NotebookDiffEditorInput extends EditorInput {
}
async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
if (!this._textModel || !this.viewType) {
if (!this._modifiedTextModel || !this.viewType) {
return undefined;
}
@ -122,7 +122,7 @@ export class NotebookDiffEditorInput extends EditorInput {
return undefined;
}
const dialogPath = this._textModel.object.resource;
const dialogPath = this._modifiedTextModel.object.resource;
const target = await this._fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems);
if (!target) {
return undefined; // save cancelled
@ -147,7 +147,7 @@ ${patterns}
`);
}
if (!await this._textModel.object.saveAs(target)) {
if (!await this._modifiedTextModel.object.saveAs(target)) {
return undefined;
}
@ -156,10 +156,10 @@ ${patterns}
// called when users rename a notebook document
rename(group: GroupIdentifier, target: URI): IMoveResult | undefined {
if (this._textModel) {
if (this._modifiedTextModel) {
const contributedNotebookProviders = this._notebookService.getContributedNotebookProviders(target);
if (contributedNotebookProviders.find(provider => provider.id === this._textModel!.object.viewType)) {
if (contributedNotebookProviders.find(provider => provider.id === this._modifiedTextModel!.object.viewType)) {
return this._move(group, target);
}
}
@ -171,8 +171,8 @@ ${patterns}
}
async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
if (this._textModel && this._textModel.object.isDirty()) {
await this._textModel.object.revert(options);
if (this._modifiedTextModel && this._modifiedTextModel.object.isDirty()) {
await this._modifiedTextModel.object.revert(options);
}
return;
@ -183,14 +183,19 @@ ${patterns}
return null;
}
if (!this._textModel) {
this._textModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType!);
if (!this._modifiedTextModel) {
this._modifiedTextModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType!);
this._register(this._modifiedTextModel.object.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
if (this._modifiedTextModel.object.isDirty() !== this._defaultDirtyState) {
this._onDidChangeDirty.fire();
}
}
if (!this._originalTextModel) {
this._originalTextModel = await this._notebookModelResolverService.resolve(this.originalResource, this.viewType!);
}
return new NotebookDiffEditorModel(this._originalTextModel.object, this._textModel.object);
return new NotebookDiffEditorModel(this._originalTextModel.object, this._modifiedTextModel.object);
}
matches(otherInput: unknown): boolean {
@ -205,8 +210,8 @@ ${patterns}
}
dispose() {
this._textModel?.dispose();
this._textModel = null;
this._modifiedTextModel?.dispose();
this._modifiedTextModel = null;
this._originalTextModel?.dispose();
this._originalTextModel = null;
super.dispose();

View file

@ -29,7 +29,7 @@ export class NotebookEditorInput extends EditorInput {
private readonly _name: string;
private _textModel: IReference<IResolvedNotebookEditorModel> | null = null;
private _editorModelReference: IReference<IResolvedNotebookEditorModel> | null = null;
private _defaultDirtyState: boolean = false;
constructor(
@ -56,14 +56,14 @@ export class NotebookEditorInput extends EditorInput {
}
isDirty() {
if (!this._textModel) {
return !!this._defaultDirtyState;
if (!this._editorModelReference) {
return this._defaultDirtyState;
}
return this._textModel.object.isDirty();
return this._editorModelReference.object.isDirty();
}
isUntitled(): boolean {
return this._textModel?.object.isUntitled() || false;
return this._editorModelReference?.object.isUntitled() || false;
}
isReadonly() {
@ -71,12 +71,12 @@ export class NotebookEditorInput extends EditorInput {
}
async save(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
if (this._textModel) {
if (this._editorModelReference) {
if (this.isUntitled()) {
return this.saveAs(group, options);
} else {
await this._textModel.object.save();
await this._editorModelReference.object.save(options);
}
return this;
@ -86,7 +86,7 @@ export class NotebookEditorInput extends EditorInput {
}
async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
if (!this._textModel) {
if (!this._editorModelReference) {
return undefined;
}
@ -96,7 +96,7 @@ export class NotebookEditorInput extends EditorInput {
return undefined;
}
const dialogPath = this.isUntitled() ? await this._suggestName(this._name) : this._textModel.object.resource;
const dialogPath = this.isUntitled() ? await this._suggestName(this._name) : this._editorModelReference.object.resource;
const target = await this._fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems);
if (!target) {
@ -122,7 +122,7 @@ ${patterns}
`);
}
if (!await this._textModel.object.saveAs(target)) {
if (!await this._editorModelReference.object.saveAs(target)) {
return undefined;
}
@ -135,27 +135,25 @@ ${patterns}
// called when users rename a notebook document
rename(group: GroupIdentifier, target: URI): IMoveResult | undefined {
if (this._textModel) {
if (this._editorModelReference) {
const contributedNotebookProviders = this._notebookService.getContributedNotebookProviders(target);
if (contributedNotebookProviders.find(provider => provider.id === this._textModel!.object.viewType)) {
if (contributedNotebookProviders.find(provider => provider.id === this._editorModelReference!.object.viewType)) {
return this._move(group, target);
}
}
return undefined;
}
private _move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined {
private _move(_group: GroupIdentifier, newResource: URI): { editor: IEditorInput } {
const editorInput = NotebookEditorInput.create(this._instantiationService, newResource, this.viewType);
return { editor: editorInput };
}
async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
if (this._textModel && this._textModel.object.isDirty()) {
await this._textModel.object.revert(options);
async revert(_group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
if (this._editorModelReference && this._editorModelReference.object.isDirty()) {
await this._editorModelReference.object.revert(options);
}
return;
}
async resolve(): Promise<IResolvedNotebookEditorModel | null> {
@ -163,20 +161,20 @@ ${patterns}
return null;
}
if (!this._textModel) {
this._textModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType);
if (!this._editorModelReference) {
this._editorModelReference = await this._notebookModelResolverService.resolve(this.resource, this.viewType);
if (this.isDisposed()) {
this._textModel.dispose();
this._textModel = null;
this._editorModelReference.dispose();
this._editorModelReference = null;
return null;
}
this._register(this._textModel.object.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
if (this._textModel.object.isDirty()) {
this._register(this._editorModelReference.object.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
if (this._editorModelReference.object.isDirty()) {
this._onDidChangeDirty.fire();
}
}
return this._textModel.object;
return this._editorModelReference.object;
}
matches(otherInput: unknown): boolean {
@ -190,8 +188,8 @@ ${patterns}
}
dispose() {
this._textModel?.dispose();
this._textModel = null;
this._editorModelReference?.dispose();
this._editorModelReference = null;
super.dispose();
}
}

View file

@ -26,12 +26,12 @@ import { Range } from 'vs/editor/common/core/range';
import { IEditor } from 'vs/editor/common/editorCommon';
import { IModeService } from 'vs/editor/common/services/modeService';
import * as nls from 'vs/nls';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { createActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenu, IMenuService, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
@ -69,6 +69,10 @@ import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { isWeb } from 'vs/base/common/platform';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
const $ = DOM.$;
@ -187,6 +191,7 @@ export class ListViewInfoAccessor extends Disposable {
export class NotebookEditorWidget extends Disposable implements INotebookEditor {
private static readonly EDITOR_MEMENTOS = new Map<string, EditorMemento<unknown>>();
private _overlayContainer!: HTMLElement;
private _notebookTopToolbarContainer!: HTMLElement;
private _body!: HTMLElement;
private _overflowContainer!: HTMLElement;
private _webview: BackLayerWebView<ICommonCellInfo> | null = null;
@ -214,6 +219,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
private readonly _memento: Memento;
private readonly _onDidFocusEmitter = this._register(new Emitter<void>());
public readonly onDidFocus = this._onDidFocusEmitter.event;
private readonly _onDidBlurEmitter = this._register(new Emitter<void>());
public readonly onDidBlur = this._onDidBlurEmitter.event;
private readonly _insetModifyQueueByOutputId = new SequencerByKey<string>();
private _kernelManger: NotebookEditorKernelManager;
private _cellContextKeyManager: CellContextKeyManager | null = null;
@ -328,6 +335,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
@IThemeService private readonly themeService: IThemeService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IModeService private readonly modeService: IModeService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@optional(ITASExperimentService) private readonly experimentService: ITASExperimentService
) {
super();
this.isEmbedded = creationOptions.isEmbedded || false;
@ -509,6 +518,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
private _createBody(parent: HTMLElement): void {
this._notebookTopToolbarContainer = document.createElement('div');
this._notebookTopToolbarContainer.classList.add('notebook-top-toolbar');
DOM.append(parent, this._notebookTopToolbarContainer);
this._body = document.createElement('div');
this._body.classList.add('cell-list-container');
this._createCellList();
@ -653,6 +665,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
const widgetFocusTracker = DOM.trackFocus(this.getDomNode());
this._register(widgetFocusTracker);
this._register(widgetFocusTracker.onDidFocus(() => this._onDidFocusEmitter.fire()));
this._register(widgetFocusTracker.onDidBlur(() => this._onDidBlurEmitter.fire()));
this._reigsterNotebookActionsToolbar();
this._register(this.onDidFocus(() => {
this._showNotebookActionsinEditorToolbar();
}));
this._register(this.onDidBlur(() => {
this._editorToolbarDisposable?.dispose();
this._toolbarActionDisposable.clear();
}));
}
private showListContextMenu(e: IListContextMenuEvent<CellViewModel>) {
@ -668,6 +690,111 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
});
}
private _notebookGlobalActionsMenu!: IMenu;
private _toolbarActionDisposable = this._register(new DisposableStore());
private _topToolbar!: ToolBar;
private _useGlobalToolbar: boolean = false;
private _editorToolbarDisposable: IDisposable | null = null;
private _reigsterNotebookActionsToolbar() {
const cellMenu = this.instantiationService.createInstance(CellMenus);
this._notebookGlobalActionsMenu = this._register(cellMenu.getNotebookToolbar(this.scopedContextKeyService));
this._register(this._notebookGlobalActionsMenu);
this._useGlobalToolbar = this.configurationService.getValue<boolean | undefined>('notebook.experimental.globalToolbar') ?? false;
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('notebook.experimental.globalToolbar')) {
this._useGlobalToolbar = this.configurationService.getValue<boolean>('notebook.experimental.globalToolbar');
this._showNotebookActionsinEditorToolbar();
}
}));
this._topToolbar = new ToolBar(this._notebookTopToolbarContainer, this.contextMenuService, {
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
actionViewItemProvider: action => {
return createActionViewItem(this.instantiationService, action);
},
renderDropdownAsChildElement: true
});
this._register(this._topToolbar);
this._showNotebookActionsinEditorToolbar();
this._register(this._notebookGlobalActionsMenu.onDidChange(() => {
this._showNotebookActionsinEditorToolbar();
}));
if (this.experimentService) {
this.experimentService.getTreatment<boolean>('nbtoolbarineditor').then(treatment => {
if (treatment === undefined) {
return;
}
if (this._useGlobalToolbar !== treatment) {
this._useGlobalToolbar = treatment;
this._showNotebookActionsinEditorToolbar();
}
});
}
}
private _showNotebookActionsinEditorToolbar() {
if (!this._useGlobalToolbar) {
// schedule actions registration in next frame, otherwise we are seeing duplicated notbebook actions temporarily
this._editorToolbarDisposable?.dispose();
this._editorToolbarDisposable = DOM.scheduleAtNextAnimationFrame(() => {
const groups = this._notebookGlobalActionsMenu.getActions({ shouldForwardArgs: true });
this._toolbarActionDisposable.clear();
this._topToolbar.setActions([], []);
if (!this.viewModel) {
return;
}
if (!this._isVisible || !this.hasFocus()) {
return;
}
groups.forEach(group => {
const groupName = group[0];
const actions = group[1];
let order = groupName === 'navigation' ? -10 : 0;
for (let i = 0; i < actions.length; i++) {
const menuItemAction = actions[i] as MenuItemAction;
this._toolbarActionDisposable.add(MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: menuItemAction.item.id,
title: menuItemAction.item.title + ' ' + this.viewModel?.uri.scheme,
category: menuItemAction.item.category,
tooltip: menuItemAction.item.tooltip,
icon: menuItemAction.item.icon,
precondition: menuItemAction.item.precondition,
toggled: menuItemAction.item.toggled,
},
title: menuItemAction.item.title + ' ' + this.viewModel?.uri.scheme,
group: groupName,
order: order
}));
order++;
}
});
});
this._notebookTopToolbarContainer.style.display = 'none';
} else {
this._toolbarActionDisposable.clear();
this._topToolbar.setActions([], []);
const groups = this._notebookGlobalActionsMenu.getActions({ shouldForwardArgs: true });
this._notebookTopToolbarContainer.style.display = 'flex';
const primaryGroup = groups.find(group => group[0] === 'navigation');
const primaryActions = primaryGroup ? primaryGroup[1] : [];
const secondaryActions = groups.filter(group => group[0] !== 'navigation').reduce((prev: (MenuItemAction | SubmenuItemAction)[], curr) => { prev.push(...curr[1]); return prev; }, []);
this._topToolbar.setActions(primaryActions, secondaryActions);
}
if (this._dimension && this._isVisible) {
this.layout(this._dimension);
}
}
private _updateForCursorNavigationMode(applyFocusChange: () => void): void {
if (this._cursorNavigationMode) {
// Will fire onDidChangeFocus, resetting the state to Container
@ -1252,7 +1379,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
this._dimension = new DOM.Dimension(dimension.width, dimension.height);
DOM.size(this._body, dimension.width, dimension.height);
DOM.size(this._body, dimension.width, dimension.height - (this._useGlobalToolbar ? /** Toolbar height */ 26 : 0));
this._list.updateOptions({ additionalScrollHeight: this._scrollBeyondLastLine ? dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP : 0 });
this._list.layout(dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, dimension.width);
@ -1314,6 +1441,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
const focused = DOM.isAncestor(document.activeElement, this._overlayContainer);
this._editorFocus.set(focused);
this.viewModel?.setFocus(focused);
if (!focused) {
this._editorToolbarDisposable?.dispose();
this._toolbarActionDisposable.clear();
}
}
hasFocus() {

View file

@ -6,7 +6,6 @@
import { localize } from 'vs/nls';
import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser';
import { flatten } from 'vs/base/common/arrays';
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { Iterable } from 'vs/base/common/iterator';
@ -31,7 +30,7 @@ import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, DisplayOrderKey
import { NotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer';
import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer';
import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { ComplexNotebookProviderInfo, IMainNotebookController, INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { Extensions as EditorExtensions, IEditorTypesHandler, IEditorType, IEditorAssociationsRegistry } from 'vs/workbench/browser/editor';
@ -233,16 +232,13 @@ class ModelData implements IDisposable {
}
}
interface INotebookProviderData {
controller: IMainNotebookController;
extensionData: NotebookExtensionDescription;
}
export class NotebookService extends Disposable implements INotebookService, IEditorTypesHandler {
declare readonly _serviceBrand: undefined;
private readonly _notebookProviders = new Map<string, INotebookProviderData>();
private readonly _notebookProviders = new Map<string, ComplexNotebookProviderInfo | SimpleNotebookProviderInfo>();
private readonly _notebookProviderInfoStore: NotebookProviderInfoStore;
private readonly _notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore();
private readonly _markdownRenderersInfos = new Set<INotebookMarkdownRendererInfo>();
@ -254,9 +250,6 @@ export class NotebookService extends Disposable implements INotebookService, IEd
readonly onDidAddNotebookDocument = this._onDidAddNotebookDocument.event;
readonly onDidRemoveNotebookDocument = this._onDidRemoveNotebookDocument.event;
private readonly _onNotebookDocumentSaved: Emitter<URI> = this._register(new Emitter<URI>());
readonly onNotebookDocumentSaved: Event<URI> = this._onNotebookDocumentSaved.event;
private readonly _onDidChangeEditorTypes = this._register(new Emitter<void>());
onDidChangeEditorTypes: Event<void> = this._onDidChangeEditorTypes.event;
@ -430,11 +423,15 @@ export class NotebookService extends Disposable implements INotebookService, IEd
return this._notebookProviders.has(viewType);
}
registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): IDisposable {
private _registerProviderData(viewType: string, data: SimpleNotebookProviderInfo | ComplexNotebookProviderInfo): void {
if (this._notebookProviders.has(viewType)) {
throw new Error(`notebook controller for viewtype '${viewType}' already exists`);
}
this._notebookProviders.set(viewType, { extensionData, controller });
this._notebookProviders.set(viewType, data);
}
registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): IDisposable {
this._registerProviderData(viewType, new ComplexNotebookProviderInfo(viewType, controller, extensionData));
if (controller.viewOptions && !this._notebookProviderInfoStore.get(viewType)) {
// register this content provider to the static contribution, if it does not exist
@ -465,6 +462,26 @@ export class NotebookService extends Disposable implements INotebookService, IEd
});
}
registerNotebookSerializer(viewType: string, extensionData: NotebookExtensionDescription, serializer: INotebookSerializer): IDisposable {
this._registerProviderData(viewType, new SimpleNotebookProviderInfo(viewType, serializer, extensionData));
return toDisposable(() => {
this._notebookProviders.delete(viewType);
});
}
async withNotebookDataProvider(resource: URI): Promise<ComplexNotebookProviderInfo | SimpleNotebookProviderInfo> {
const [first] = this._notebookProviderInfoStore.getContributedNotebook(resource);
if (!first) {
throw new Error(`NO contribution for resource: '${resource.toString()}'`);
}
await this.canResolve(first.id);
const result = this._notebookProviders.get(first.id);
if (!result) {
throw new Error(`NO provider registered for view type: '${first.id}'`);
}
return result;
}
registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable {
const d = this._notebookKernelProviderInfoStore.add(provider);
const kernelChangeEventListener = provider.onDidChangeKernels((e) => {
@ -503,47 +520,6 @@ export class NotebookService extends Disposable implements INotebookService, IEd
return Array.from(this._markdownRenderersInfos);
}
// --- notebook documents: IO
private _withProvider(viewType: string): INotebookProviderData {
const result = this._notebookProviders.get(viewType);
if (!result) {
throw new Error(`having NO provider for ${viewType}`);
}
return result;
}
async fetchNotebookRawData(viewType: string, uri: URI, backupId: string | undefined, token: CancellationToken, untitledDocumentData?: VSBuffer): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions }> {
if (!await this.canResolve(viewType)) {
throw new Error(`CANNOT fetch notebook data, there is NO provider for '${viewType}'`);
}
const provider = this._withProvider(viewType)!;
return await provider.controller.openNotebook(viewType, uri, backupId, token, untitledDocumentData);
}
async save(viewType: string, resource: URI, token: CancellationToken): Promise<boolean> {
const provider = this._withProvider(viewType);
const ret = await provider.controller.save(resource, token);
if (ret) {
this._onNotebookDocumentSaved.fire(resource);
}
return ret;
}
async saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise<boolean> {
const provider = this._withProvider(viewType);
const ret = await provider.controller.saveAs(resource, target, token);
if (ret) {
this._onNotebookDocumentSaved.fire(resource);
}
return ret;
}
async backup(viewType: string, uri: URI, token: CancellationToken): Promise<string | undefined> {
const provider = this._withProvider(viewType);
return provider.controller.backup(uri, token);
}
// --- notebook documents: create, destory, retrieve, enumerate
createNotebookTextModel(viewType: string, uri: URI, data: NotebookDataDto, transientOptions: TransientOptions): NotebookTextModel {
@ -568,17 +544,10 @@ export class NotebookService extends Disposable implements INotebookService, IEd
return [...this._models].map(e => e[1].model);
}
destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void {
this._onWillDisposeDocument(notebook);
}
private _onWillDisposeDocument(model: INotebookTextModel): void {
const modelData = this._models.get(model.uri);
this._models.delete(model.uri);
if (modelData) {
modelData.model.dispose();
this._models.delete(model.uri);
modelData.dispose();
this._onDidRemoveNotebookDocument.fire(modelData.model.uri);
}
@ -675,17 +644,18 @@ export class NotebookService extends Disposable implements INotebookService, IEd
return ret;
}
// --- data provider IPC (deprecated)
async resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise<void> {
const entry = this._notebookProviders.get(viewType);
if (entry) {
if (entry instanceof ComplexNotebookProviderInfo) {
entry.controller.resolveNotebookEditor(viewType, uri, editorId);
}
}
onDidReceiveMessage(viewType: string, editorId: string, rendererType: string | undefined, message: any): void {
const provider = this._notebookProviders.get(viewType);
if (provider) {
if (provider instanceof ComplexNotebookProviderInfo) {
return provider.controller.onDidReceiveMessage(editorId, rendererType, message);
}
}

View file

@ -12,6 +12,10 @@ export class CellMenus {
@IMenuService private readonly menuService: IMenuService,
) { }
getNotebookToolbar(contextKeyService: IContextKeyService): IMenu {
return this.getMenu(MenuId.NotebookToolbar, contextKeyService);
}
getCellTitleMenu(contextKeyService: IContextKeyService): IMenu {
return this.getMenu(MenuId.NotebookCellTitle, contextKeyService);
}

View file

@ -67,7 +67,9 @@ export class CellOutputElement extends Disposable {
}
detach() {
this.domNode.parentElement?.removeChild(this.domNode);
if (this.domNode) {
this.domNode.parentElement?.removeChild(this.domNode);
}
}
updateDOMTop(top: number) {

View file

@ -18,10 +18,8 @@ import { IAccessibilityInformation } from 'vs/platform/accessibility/common/acce
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IEditorModel } from 'vs/platform/editor/common/editor';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IRevertOptions } from 'vs/workbench/common/editor';
import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
export enum CellKind {
@ -179,7 +177,7 @@ export interface INotebookTextModel {
readonly versionId: number;
readonly cells: readonly ICell[];
onWillDispose(listener: () => void): IDisposable;
onWillDispose: Event<void>;
}
export type NotebookCellTextModelSplice<T> = [
@ -604,7 +602,7 @@ export interface INotebookLoadOptions {
/**
* Go to disk bypassing any cache of the model if any.
*/
forceReadFromDisk?: boolean;
forceReadFromFile?: boolean;
}
export interface IResolvedNotebookEditorModel extends INotebookEditorModel {
@ -613,17 +611,17 @@ export interface IResolvedNotebookEditorModel extends INotebookEditorModel {
export interface INotebookEditorModel extends IEditorModel {
readonly onDidChangeDirty: Event<void>;
readonly onDidSave: Event<void>;
readonly resource: URI;
readonly viewType: string;
readonly notebook: NotebookTextModel | undefined;
readonly lastResolvedFileStat: IFileStatWithMetadata | undefined;
isResolved(): this is IResolvedNotebookEditorModel;
isDirty(): boolean;
isUntitled(): boolean;
load(options?: INotebookLoadOptions): Promise<IResolvedNotebookEditorModel>;
save(): Promise<boolean>;
save(options?: ISaveOptions): Promise<boolean>;
saveAs(target: URI): Promise<boolean>;
revert(options?: IRevertOptions | undefined): Promise<void>;
revert(options?: IRevertOptions): Promise<void>;
}
export interface INotebookDiffEditorModel extends IEditorModel {

View file

@ -4,11 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { EditorModel, IRevertOptions } from 'vs/workbench/common/editor';
import { EditorModel, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
import { Emitter, Event } from 'vs/base/common/event';
import { CellEditType, CellKind, ICellEditOperation, INotebookEditorModel, INotebookLoadOptions, IResolvedNotebookEditorModel, NotebookCellsChangeType, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellEditType, CellKind, ICellEditOperation, INotebookEditorModel, INotebookLoadOptions, IResolvedNotebookEditorModel, NotebookCellsChangeType, NotebookDataDto, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IMainNotebookController, INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService';
import { URI } from 'vs/base/common/uri';
import { IWorkingCopyService, IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { CancellationToken } from 'vs/base/common/cancellation';
@ -19,17 +19,23 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
import { ILabelService } from 'vs/platform/label/common/label';
import { ILogService } from 'vs/platform/log/common/log';
import { TaskSequentializer } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { bufferToStream, streamToBuffer, VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer';
import { assertType } from 'vs/base/common/types';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { IFileWorkingCopyModel, IFileWorkingCopyModelContentChangedEvent, IFileWorkingCopyModelFactory, IResolvedFileWorkingCopy } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy';
import { IDisposable } from 'vs/base/common/lifecycle';
import { canceled } from 'vs/base/common/errors';
export class NotebookEditorModel extends EditorModel implements INotebookEditorModel {
//#region --- complex content provider
export class ComplexNotebookEditorModel extends EditorModel implements INotebookEditorModel {
private readonly _onDidSave = this._register(new Emitter<void>());
private readonly _onDidChangeDirty = this._register(new Emitter<void>());
private readonly _onDidChangeContent = this._register(new Emitter<void>());
readonly onDidSave = this._onDidSave.event;
readonly onDidChangeDirty = this._onDidChangeDirty.event;
readonly onDidChangeContent = this._onDidChangeContent.event;
private _lastResolvedFileStat?: IFileStatWithMetadata;
@ -42,6 +48,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM
constructor(
readonly resource: URI,
readonly viewType: string,
private readonly _contentProvider: IMainNotebookController,
@INotebookService private readonly _notebookService: INotebookService,
@IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService,
@IBackupFileService private readonly _backupFileService: IBackupFileService,
@ -62,7 +69,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM
get name() { return that._name; }
readonly capabilities = that.isUntitled() ? WorkingCopyCapabilities.Untitled : WorkingCopyCapabilities.None;
readonly onDidChangeDirty = that.onDidChangeDirty;
readonly onDidChangeContent = that.onDidChangeContent;
readonly onDidChangeContent = that._onDidChangeContent.event;
isDirty(): boolean { return that.isDirty(); }
backup(token: CancellationToken): Promise<IWorkingCopyBackup> { return that.backup(token); }
save(): Promise<boolean> { return that.save(); }
@ -82,7 +89,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM
const stats = await this._resolveStats(this.resource);
if (stats && this._lastResolvedFileStat && stats.etag !== this._lastResolvedFileStat.etag) {
this._logService.debug('[notebook editor model] trigger load after file event');
this.load({ forceReadFromDisk: true });
this.load({ forceReadFromFile: true });
}
}));
}
@ -99,10 +106,6 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM
return this.resource.scheme === Schemas.untitled;
}
get lastResolvedFileStat(): IFileStatWithMetadata | undefined {
return this._lastResolvedFileStat;
}
get notebook(): NotebookTextModel | undefined {
const candidate = this._notebookService.getNotebookTextModel(this.resource);
return candidate && candidate.viewType === this.viewType ? candidate : undefined;
@ -121,7 +124,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM
return {};
}
const backupId = await this._notebookService.backup(this.viewType, this.resource, token);
const backupId = await this._contentProvider.backup(this.resource, token);
if (token.isCancellationRequested) {
return {};
}
@ -131,7 +134,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM
meta: {
mtime: stats?.mtime ?? Date.now(),
viewType: this.notebook.viewType,
backupId: backupId
backupId
}
};
}
@ -142,7 +145,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM
return;
}
await this.load({ forceReadFromDisk: true });
await this.load({ forceReadFromFile: true });
const newStats = await this._resolveStats(this.resource);
this._lastResolvedFileStat = newStats;
@ -150,8 +153,8 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM
this._onDidChangeDirty.fire();
}
async load(options?: INotebookLoadOptions): Promise<NotebookEditorModel & IResolvedNotebookEditorModel> {
if (options?.forceReadFromDisk) {
async load(options?: INotebookLoadOptions): Promise<IResolvedNotebookEditorModel> {
if (options?.forceReadFromFile) {
this._logService.debug('[notebook editor model] load from provider (forceRead)', this.resource.toString());
this._loadFromProvider(undefined);
assertType(this.isResolved());
@ -188,7 +191,9 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM
private async _loadFromProvider(backupId: string | undefined): Promise<void> {
const data = await this._notebookService.fetchNotebookRawData(this.viewType, this.resource, backupId, CancellationToken.None, (await this.getUntitledDocumentData(this.resource)));
const untitledData = await this.getUntitledDocumentData(this.resource);
const data = await this._contentProvider.open(this.resource, backupId, untitledData, CancellationToken.None);
this._lastResolvedFileStat = await this._resolveStats(this.resource);
if (this.isDisposed()) {
@ -326,10 +331,13 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM
if (!this.isResolved()) {
return;
}
await this._notebookService.save(this.notebook.viewType, this.notebook.uri, CancellationToken.None);
this._logService.debug(`[notebook editor model] save(${versionId}) - document saved saved, start updating file stats`, this.resource.toString(true));
const success = await this._contentProvider.save(this.notebook.uri, CancellationToken.None);
this._logService.debug(`[notebook editor model] save(${versionId}) - document saved saved, start updating file stats`, this.resource.toString(true), success);
this._lastResolvedFileStat = await this._resolveStats(this.resource);
this.setDirty(false);
if (success) {
this.setDirty(false);
this._onDidSave.fire();
}
})()).then(() => {
return true;
});
@ -353,10 +361,13 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM
return true;
}
await this._notebookService.saveAs(this.notebook.viewType, this.notebook.uri, targetResource, CancellationToken.None);
this._logService.debug(`[notebook editor model] saveAs - document saved, start updating file stats`, this.resource.toString(true));
const success = await this._contentProvider.saveAs(this.notebook.uri, targetResource, CancellationToken.None);
this._logService.debug(`[notebook editor model] saveAs - document saved, start updating file stats`, this.resource.toString(true), success);
this._lastResolvedFileStat = await this._resolveStats(this.resource);
this.setDirty(false);
if (success) {
this.setDirty(false);
this._onDidSave.fire();
}
return true;
}
@ -375,3 +386,177 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM
}
}
}
//#endregion
//#region --- simple content provider
export class SimpleNotebookEditorModel extends EditorModel implements INotebookEditorModel {
readonly onDidChangeDirty: Event<void>;
readonly onDidSave: Event<void>;
readonly resource: URI;
readonly viewType: string;
readonly notebook: NotebookTextModel;
constructor(
private readonly _workingCopy: IResolvedFileWorkingCopy<NotebookFileWorkingCopyModel>
) {
super();
this.resource = _workingCopy.resource;
this.viewType = _workingCopy.model.notebookModel.viewType;
this.notebook = _workingCopy.model.notebookModel;
this.onDidChangeDirty = Event.signal(_workingCopy.onDidChangeDirty);
this.onDidSave = Event.signal(_workingCopy.onDidSave);
}
dispose(): void {
this._workingCopy.dispose();
super.dispose();
}
isDirty(): boolean {
return this._workingCopy.isDirty();
}
revert(options?: IRevertOptions): Promise<void> {
return this._workingCopy.revert(options);
}
save(options?: ISaveOptions): Promise<boolean> {
return this._workingCopy.save(options);
}
isUntitled(): boolean {
return this.resource.scheme === Schemas.untitled;
}
async load(options?: INotebookLoadOptions): Promise<IResolvedNotebookEditorModel> {
// todo@bpasero,jrieken is this right?
// resolve return a working copy but I believe it is always THIS
await this._workingCopy.resolve(options);
return this;
}
saveAs(target: URI): Promise<boolean> {
throw new Error('Method not implemented.');
}
}
export class NotebookFileWorkingCopyModel implements IFileWorkingCopyModel {
private readonly _onDidChangeContent = new Emitter<IFileWorkingCopyModelContentChangedEvent>();
private readonly _changeListener: IDisposable;
readonly onDidChangeContent = this._onDidChangeContent.event;
readonly onWillDispose: Event<void>;
constructor(
private readonly _notebookModel: NotebookTextModel,
private readonly _notebookSerializer: INotebookSerializer
) {
this.onWillDispose = _notebookModel.onWillDispose.bind(_notebookModel);
this._changeListener = _notebookModel.onDidChangeContent(e => {
for (const rawEvent of e.rawEvents) {
if (rawEvent.kind === NotebookCellsChangeType.Initialize) {
continue;
}
if (rawEvent.transient) {
continue;
}
//todo@jrieken,@rebornix forward this information from notebook model
this._onDidChangeContent.fire({
isRedoing: false,
isUndoing: false
});
break;
}
});
}
dispose(): void {
this._changeListener.dispose();
this._onDidChangeContent.dispose();
this._notebookModel.dispose();
}
get notebookModel() {
return this._notebookModel;
}
async snapshot(token: CancellationToken): Promise<VSBufferReadableStream> {
const data: NotebookDataDto = {
metadata: this._notebookModel.metadata,
cells: [],
};
for (const cell of this._notebookModel.cells) {
data.cells.push({
cellKind: cell.cellKind,
language: cell.language,
source: cell.getValue(),
outputs: cell.outputs
});
}
const bytes = await this._notebookSerializer.notebookToData(data);
if (token.isCancellationRequested) {
throw canceled();
}
return bufferToStream(bytes);
}
async update(stream: VSBufferReadableStream, token: CancellationToken): Promise<void> {
const bytes = await streamToBuffer(stream);
const data = await this._notebookSerializer.dataToNotebook(bytes);
if (token.isCancellationRequested) {
return;
}
this._notebookModel.metadata = data.metadata;
this._notebookModel.transientOptions = this._notebookSerializer.options;
const edits: ICellEditOperation[] = [{ editType: CellEditType.Replace, index: 0, count: this._notebookModel.cells.length, cells: data.cells }];
this._notebookModel.applyEdits(edits, true, undefined, () => undefined, undefined, false);
}
getAlternativeVersionId(): number {
//todo@jrieken,@rebornix -> add alternative version id
return this._notebookModel.versionId;
}
pushStackElement(): void {
this._notebookModel.pushStackElement(nls.localize('save', 'Save Notebook'), undefined, undefined);
}
}
export class NotebookFileWorkingCopyModelFactory implements IFileWorkingCopyModelFactory<NotebookFileWorkingCopyModel>{
constructor(
@INotebookService private readonly _notebookService: INotebookService
) { }
async createModel(resource: URI, stream: VSBufferReadableStream, token: CancellationToken): Promise<NotebookFileWorkingCopyModel> {
const info = await this._notebookService.withNotebookDataProvider(resource);
if (!(info instanceof SimpleNotebookProviderInfo)) {
throw new Error('CANNOT open file notebook with this provider');
}
const data = await info.serializer.dataToNotebook(await streamToBuffer(stream));
if (token.isCancellationRequested) {
throw canceled();
}
const notebookModel = this._notebookService.createNotebookTextModel(info.viewType, resource, data, info.serializer.options);
return new NotebookFileWorkingCopyModel(notebookModel, info.serializer);
}
}
//#endregion

View file

@ -6,21 +6,31 @@
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { CellUri, IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
import { ComplexNotebookEditorModel, NotebookFileWorkingCopyModel, NotebookFileWorkingCopyModelFactory, SimpleNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
import { combinedDisposable, DisposableStore, IDisposable, IReference, ReferenceCollection } from 'vs/base/common/lifecycle';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { ComplexNotebookProviderInfo, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService';
import { ILogService } from 'vs/platform/log/common/log';
import { Event } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { FileWorkingCopyManager, IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager';
import { IResolvedFileWorkingCopy } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy';
export const INotebookEditorModelResolverService = createDecorator<INotebookEditorModelResolverService>('INotebookModelResolverService');
export interface INotebookEditorModelResolverService {
readonly _serviceBrand: undefined;
onDidSaveNotebook: Event<URI>;
resolve(resource: URI, viewType?: string): Promise<IReference<IResolvedNotebookEditorModel>>;
}
class NotebookModelReferenceCollection extends ReferenceCollection<Promise<IResolvedNotebookEditorModel>> {
export class NotebookModelReferenceCollection extends ReferenceCollection<Promise<IResolvedNotebookEditorModel>> {
private readonly _workingCopyManager: IFileWorkingCopyManager<NotebookFileWorkingCopyModel>;
private readonly _modelListener = new Map<IResolvedNotebookEditorModel, IDisposable>();
private readonly _onDidSaveNotebook = new Emitter<URI>();
readonly onDidSaveNotebook: Event<URI> = this._onDidSaveNotebook.event;
constructor(
@IInstantiationService readonly _instantiationService: IInstantiationService,
@ -28,18 +38,39 @@ export class NotebookModelReferenceCollection extends ReferenceCollection<Promis
@ILogService private readonly _logService: ILogService,
) {
super();
this._workingCopyManager = <any>_instantiationService.createInstance(
FileWorkingCopyManager,
new NotebookFileWorkingCopyModelFactory(_notebookService)
);
}
protected createReferencedObject(key: string, viewType: string): Promise<IResolvedNotebookEditorModel> {
protected async createReferencedObject(key: string, viewType: string): Promise<IResolvedNotebookEditorModel> {
const uri = URI.parse(key);
const model = this._instantiationService.createInstance(NotebookEditorModel, uri, viewType);
const promise = model.load();
return promise;
const info = await this._notebookService.withNotebookDataProvider(uri);
let result: IResolvedNotebookEditorModel;
if (info instanceof ComplexNotebookProviderInfo) {
const model = this._instantiationService.createInstance(ComplexNotebookEditorModel, uri, viewType, info.controller);
result = await model.load();
} else if (info instanceof SimpleNotebookProviderInfo) {
const workingCopy = await this._workingCopyManager.resolve(uri);
result = new SimpleNotebookEditorModel(<IResolvedFileWorkingCopy<NotebookFileWorkingCopyModel>>workingCopy);
} else {
throw new Error(`CANNOT open ${key}, no provider found`);
}
this._modelListener.set(result, result.onDidSave(() => this._onDidSaveNotebook.fire(result.resource)));
return result;
}
protected destroyReferencedObject(_key: string, object: Promise<IResolvedNotebookEditorModel>): void {
object.then(model => {
this._notebookService.destoryNotebookDocument(model.viewType, model.notebook);
this._modelListener.get(model)?.dispose();
this._modelListener.delete(model);
model.dispose();
}).catch(err => {
this._logService.critical('FAILED to destory notebook', err);
@ -53,11 +84,14 @@ export class NotebookModelResolverService implements INotebookEditorModelResolve
private readonly _data: NotebookModelReferenceCollection;
readonly onDidSaveNotebook: Event<URI>;
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@INotebookService private readonly _notebookService: INotebookService
) {
this._data = instantiationService.createInstance(NotebookModelReferenceCollection);
this.onDidSaveNotebook = this._data.onDidSaveNotebook;
}
async resolve(resource: URI, viewType?: string): Promise<IReference<IResolvedNotebookEditorModel>> {

View file

@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
import { Event } from 'vs/base/common/event';
import { INotebookTextModel, INotebookRendererInfo, INotebookKernelProvider, INotebookKernel, TransientMetadata, NotebookDataDto, TransientOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, IOutputDto, INotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookRendererInfo, INotebookKernelProvider, INotebookKernel, NotebookDataDto, TransientOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, IOutputDto, INotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CancellationToken } from 'vs/base/common/cancellation';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
@ -22,32 +22,56 @@ export const INotebookService = createDecorator<INotebookService>('notebookServi
export interface IMainNotebookController {
viewOptions?: { displayName: string; filenamePattern: (string | IRelativePattern | INotebookExclusiveDocumentFilter)[]; exclusive: boolean; };
options: { transientOutputs: boolean; transientMetadata: TransientMetadata; };
options: TransientOptions;
resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise<void>;
onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: any): void;
openNotebook(viewType: string, uri: URI, backupId: string | undefined, token: CancellationToken, untitledDocumentData?: VSBuffer): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions; }>;
open(uri: URI, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions; }>;
save(uri: URI, token: CancellationToken): Promise<boolean>;
saveAs(uri: URI, target: URI, token: CancellationToken): Promise<boolean>;
backup(uri: URI, token: CancellationToken): Promise<string>;
}
export interface INotebookSerializer {
options: TransientOptions;
dataToNotebook(data: VSBuffer): Promise<NotebookDataDto>
notebookToData(data: NotebookDataDto): Promise<VSBuffer>;
}
export interface INotebookRawData {
data: NotebookDataDto;
transientOptions: TransientOptions;
}
export class ComplexNotebookProviderInfo {
constructor(
readonly viewType: string,
readonly controller: IMainNotebookController,
readonly extensionData: NotebookExtensionDescription
) { }
}
export class SimpleNotebookProviderInfo {
constructor(
readonly viewType: string,
readonly serializer: INotebookSerializer,
readonly extensionData: NotebookExtensionDescription
) { }
}
export interface INotebookService {
readonly _serviceBrand: undefined;
canResolve(viewType: string): Promise<boolean>;
onDidRemoveNotebookDocument: Event<URI>;
onDidAddNotebookDocument: Event<NotebookTextModel>;
onNotebookDocumentSaved: Event<URI>;
onDidChangeKernels: Event<URI | undefined>;
onDidChangeNotebookActiveKernel: Event<{ uri: URI, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }>;
registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): IDisposable;
registerNotebookSerializer(viewType: string, extensionData: NotebookExtensionDescription, serializer: INotebookSerializer): IDisposable;
withNotebookDataProvider(resource: URI): Promise<ComplexNotebookProviderInfo | SimpleNotebookProviderInfo>;
getMimeTypeInfo(textModel: NotebookTextModel, output: IOutputDto): readonly IOrderedMimeType[];
@ -66,12 +90,6 @@ export interface INotebookService {
getContributedNotebookProviders(resource?: URI): readonly NotebookProviderInfo[];
getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined;
getNotebookProviderResourceRoots(): URI[];
destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void;
fetchNotebookRawData(viewType: string, uri: URI, backupId: string | undefined, token: CancellationToken, untitledDocumentData?: VSBuffer): Promise<INotebookRawData>;
save(viewType: string, resource: URI, token: CancellationToken): Promise<boolean>;
saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise<boolean>;
backup(viewType: string, uri: URI, token: CancellationToken): Promise<string | undefined>;
onDidReceiveMessage(viewType: string, editorId: string, rendererType: string | undefined, message: unknown): void;
setToCopy(items: NotebookCellTextModel[], isCopy: boolean): void;

View file

@ -13,8 +13,8 @@ import { IFileService } from 'vs/platform/files/common/files';
import { ILabelService } from 'vs/platform/label/common/label';
import { NullLogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { ComplexNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { IWorkingCopy, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
@ -32,6 +32,8 @@ suite('NotebookEditorModel', function () {
getUriBasenameLabel(uri: URI) { return uri.toString(); }
};
const notebookDataProvider = new class extends mock<IMainNotebookController>() { };
test('working copy uri', function () {
if (1) {
this.skip();
@ -47,8 +49,8 @@ suite('NotebookEditorModel', function () {
}
};
new NotebookEditorModel(r1, 'fff', notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService);
new NotebookEditorModel(r2, 'fff', notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService);
new ComplexNotebookEditorModel(r1, 'fff', notebookDataProvider, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService);
new ComplexNotebookEditorModel(r2, 'fff', notebookDataProvider, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService);
assert.strictEqual(copies.length, 2);
assert.strictEqual(!isEqual(copies[0].resource, copies[1].resource), true);

View file

@ -30,7 +30,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList';
import { ListViewInfoAccessor } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
import { mock } from 'vs/base/test/common/mock';
@ -53,6 +52,9 @@ export class TestCell extends NotebookCellTextModel {
export class NotebookEditorTestModel extends EditorModel implements INotebookEditorModel {
private _dirty = false;
protected readonly _onDidSave = this._register(new Emitter<void>());
readonly onDidSave = this._onDidSave.event;
protected readonly _onDidChangeDirty = this._register(new Emitter<void>());
readonly onDidChangeDirty = this._onDidChangeDirty.event;
@ -85,7 +87,6 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi
}));
}
}
lastResolvedFileStat: IFileStatWithMetadata | undefined;
isDirty() {
return this._dirty;
@ -107,6 +108,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi
if (this._notebook) {
this._dirty = false;
this._onDidChangeDirty.fire();
this._onDidSave.fire();
// todo, flush all states
return true;
}