non file based hot reload

This commit is contained in:
rebornix 2021-06-21 08:28:28 -07:00
parent e7ed3023f0
commit 2c43db93fc
8 changed files with 238 additions and 49 deletions

View file

@ -8,25 +8,26 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable } from 'vs/base/common/lifecycle';
import { parse } from 'vs/base/common/marshalling';
import { assertType } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorDescriptor, IEditorRegistry } from 'vs/workbench/browser/editor';
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { EditorExtensions, viewColumnToEditorGroup } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { EditorExtensions, IEditorInputFactoryRegistry, IEditorInputSerializer, viewColumnToEditorGroup } from 'vs/workbench/common/editor';
import { InteractiveEditor } from 'vs/workbench/contrib/interactive/browser/interactiveEditor';
import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput';
import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions';
import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
import { CellEditType, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellEditType, CellKind, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
@ -65,7 +66,36 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
contentOptions.transientDocumentMetadata = newOptions.transientDocumentMetadata;
contentOptions.transientOutputs = newOptions.transientOutputs;
},
open: async (_uri: URI, _backupId: string | undefined, _untitledDocumentData: VSBuffer | undefined, _token: CancellationToken) => {
open: async (_uri: URI, _backupId: string | VSBuffer | undefined, _untitledDocumentData: VSBuffer | undefined, _token: CancellationToken) => {
if (_backupId instanceof VSBuffer) {
const backup = _backupId.toString();
try {
const document = JSON.parse(backup) as { cells: { kind: CellKind, language: string, metadata: any, mime: string | undefined, content: string, outputs?: ICellOutput[] }[] };
return {
data: {
metadata: {},
cells: document.cells.map(cell => ({
source: cell.content,
language: cell.language,
cellKind: cell.kind,
mime: cell.mime,
outputs: cell.outputs
? cell.outputs.map(output => ({
outputId: output.outputId,
outputs: output.outputs.map(ot => ({
mime: ot.mime,
data: Uint8Array.from(ot.data)
}))
}))
: [],
metadata: cell.metadata
}))
},
transientOptions: contentOptions
};
} catch (_e) { }
}
return {
data: {
metadata: {},
@ -83,8 +113,33 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
return false;
},
backup: async (uri: URI, token: CancellationToken) => {
// return this._proxy.$backupNotebook(viewType, uri, token);
return '';
const doc = notebookService.listNotebookDocuments().find(document => document.uri.toString() === uri.toString());
if (doc) {
const cells = doc.cells.map(cell => ({
kind: cell.cellKind,
language: cell.language,
metadata: cell.metadata,
mine: cell.mime,
outputs: cell.outputs.map(output => {
return {
outputId: output.outputId,
outputs: output.outputs.map(ot => ({
mime: ot.mime,
data: Array.from(ot.data)
}))
};
}),
content: cell.getValue()
}));
const buffer = VSBuffer.fromString(JSON.stringify({
cells: cells
}));
return buffer;
} else {
return '';
}
}
};
this._register(notebookService.registerNotebookController('interactive', {
@ -92,20 +147,57 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
location: URI.parse('interactive://test')
}, controller));
this._register(notebookService.registerContributedNotebookType('interactive', {
extension: new ExtensionIdentifier('interactive.builtin'),
providerDisplayName: 'Interactive Notebook',
displayName: 'Interactive',
filenamePattern: ['*.interactive'],
exclusive: true
}));
const info = notebookService.getContributedNotebookType('interactive');
if (info) {
info.update({ selectors: ['*.interactive'] });
} else {
this._register(notebookService.registerContributedNotebookType('interactive', {
providerDisplayName: 'Interactive Notebook',
displayName: 'Interactive',
filenamePattern: ['*.interactive'],
exclusive: true
}));
}
}
}
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
// TODO@rebornix, we set it to Eventually since we want to make sure the contributedEditors in notebookserviceImpl was not flushed by the extension update
workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveDocumentContribution, LifecyclePhase.Eventually);
workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveDocumentContribution, LifecyclePhase.Starting);
class InteractiveEditorSerializer implements IEditorInputSerializer {
canSerialize(): boolean {
return true;
}
serialize(input: EditorInput): string {
assertType(input instanceof InteractiveEditorInput);
return JSON.stringify({
resource: input.primary.resource,
inputResource: input.inputResource,
});
}
deserialize(instantiationService: IInstantiationService, raw: string) {
type Data = { resource: URI, inputResource: URI; };
const data = <Data>parse(raw);
if (!data) {
return undefined;
}
const { resource, inputResource } = data;
if (!data || !URI.isUri(resource) || !URI.isUri(inputResource)) {
return undefined;
}
const input = InteractiveEditorInput.create(instantiationService, resource, inputResource);
return input;
}
}
Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(
InteractiveEditorInput.ID,
InteractiveEditorSerializer
);
let counter = 1;
@ -144,7 +236,7 @@ registerAction2(class extends Action2 {
const editorService = accessor.get(IEditorService);
const editorGroupService = accessor.get(IEditorGroupsService);
const editorInput = new InteractiveEditorInput(notebookUri, undefined, inputUri, accessor.get(ILabelService), accessor.get(IFileService), accessor.get(IInstantiationService));
const editorInput = InteractiveEditorInput.create(accessor.get(IInstantiationService), notebookUri, inputUri);
const group = viewColumnToEditorGroup(editorGroupService, column);
await editorService.openEditor(editorInput, undefined, group);
counter++;

View file

@ -137,6 +137,8 @@ export class InteractiveEditor extends EditorPane {
throw new Error('?');
}
this.#widgetDisposableStore.add(model);
this.#notebookWidget.value?.setParentContextKeyService(this.#contextKeyService);
await this.#notebookWidget.value!.setModel(model.notebook, undefined);
this.#notebookWidget.value!.setOptions({

View file

@ -3,17 +3,33 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IReference } from 'vs/base/common/lifecycle';
import * as paths from 'vs/base/common/path';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { AbstractResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { IEditorInput } from 'vs/workbench/common/editor';
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
import { IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
export class InteractiveEditorInput extends AbstractResourceEditorInput implements ICompositeNotebookEditorInput {
typeId: string = 'workbench.input.interactive';
interface InteractiveEditorInputOptions {
startFresh?: boolean;
}
export class InteractiveEditorInput extends SideBySideEditorInput implements ICompositeNotebookEditorInput {
static create(instantiationService: IInstantiationService, resource: URI, inputResource: URI, options?: InteractiveEditorInputOptions) {
return instantiationService.createInstance(InteractiveEditorInput, resource, inputResource, options ?? {});
}
static override readonly ID: string = 'workbench.input.interactive';
override get typeId(): string {
return InteractiveEditorInput.ID;
}
private _notebookEditorInput: NotebookEditorInput;
get notebookEditorInput() {
@ -29,29 +45,89 @@ export class InteractiveEditorInput extends AbstractResourceEditorInput implemen
get inputResource() {
return this._inputResource;
}
private readonly _options: InteractiveEditorInputOptions;
private _inputResolver: Promise<IResolvedNotebookEditorModel | null> | null;
private _editorModelReference: IReference<IResolvedNotebookEditorModel> | null;
private readonly _notebookService: INotebookService;
private readonly _notebookModelResolverService: INotebookEditorModelResolverService;
constructor(
resource: URI,
preferredResource: URI | undefined,
inputResource: URI,
@ILabelService labelService: ILabelService,
@IFileService fileService: IFileService,
options: InteractiveEditorInputOptions,
@IInstantiationService instantiationService: IInstantiationService,
@INotebookService notebookService: INotebookService,
@INotebookEditorModelResolverService notebookModelResolverService: INotebookEditorModelResolverService,
) {
super(resource, preferredResource, labelService, fileService);
// do something similar to untitled file
this._notebookEditorInput = NotebookEditorInput.create(instantiationService, resource, 'interactive', {});
const input = NotebookEditorInput.create(instantiationService, resource, 'interactive', {});
super(undefined, undefined, input, input);
this._notebookEditorInput = input;
this._options = options;
this._register(this._notebookEditorInput);
this._inputResource = inputResource;
this._inputResolver = null;
this._editorModelReference = null;
this._notebookService = notebookService;
this._notebookModelResolverService = notebookModelResolverService;
}
override isDirty() {
return false;
}
private async _resolve() {
if (!await this._notebookService.canResolve('interactive')) {
return null;
}
if (!this._editorModelReference) {
this._editorModelReference = await this._notebookModelResolverService.resolve(this.primary.resource!, 'interactive');
if (this.isDisposed()) {
this._editorModelReference.dispose();
this._editorModelReference = null;
return null;
}
} else {
await this._editorModelReference.object.load();
}
if (!this._editorModelReference.object.notebook) {
await this._editorModelReference.object.load();
}
return this._editorModelReference.object;
}
override async resolve(): Promise<IResolvedNotebookEditorModel | null> {
return this._notebookEditorInput.resolve();
if (this._inputResolver) {
return this._inputResolver;
}
this._inputResolver = this._resolve();
return this._inputResolver;
}
override matches(otherInput: IEditorInput | IResourceEditorInputType): boolean {
if (super.matches(otherInput)) {
return true;
}
if (otherInput instanceof InteractiveEditorInput) {
return isEqual(this.resource, otherInput.resource);
}
return false;
}
override getName() {
const p = this.resource.path;
const p = this.primary.resource!.path;
const basename = paths.basename(p);
return basename.substr(0, basename.length - paths.extname(p).length);
}
override dispose() {
this._editorModelReference?.dispose();
this._editorModelReference = null;
super.dispose();
}
}

View file

@ -88,8 +88,13 @@ export class NotebookProviderInfoStore extends Disposable {
private _setupHandler(extensions: readonly IExtensionPointUser<INotebookEditorContribution[]>[]) {
this._handled = true;
const builtins: NotebookProviderInfo[] = [...this._contributedEditors.values()].filter(info => !info.extension);
this._clear();
builtins.forEach(builtin => {
this.add(builtin);
});
for (const extension of extensions) {
for (const notebookContribution of extension.value) {

View file

@ -463,7 +463,7 @@ export interface NotebookData {
export interface INotebookContributionData {
extension: ExtensionIdentifier,
extension?: ExtensionIdentifier,
providerDisplayName: string;
displayName: string;
filenamePattern: (string | glob.IRelativePattern | INotebookExclusiveDocumentFilter)[];

View file

@ -14,14 +14,14 @@ import { URI } from 'vs/base/common/uri';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities, NO_TYPE_ID, IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
import { IResolvedWorkingCopyBackup, IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
import { Schemas } from 'vs/base/common/network';
import { IFileStatWithMetadata, IFileService, FileChangeType, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILogService } from 'vs/platform/log/common/log';
import { TaskSequentializer } from 'vs/base/common/async';
import { bufferToStream, streamToBuffer, VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer';
import { bufferToReadable, 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 { StoredFileWorkingCopyState, IStoredFileWorkingCopy, IStoredFileWorkingCopyModel, IStoredFileWorkingCopyModelContentChangedEvent, IStoredFileWorkingCopyModelFactory } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy';
@ -160,19 +160,26 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook
return {};
}
const backupId = await this._contentProvider.backup(this.resource, token);
const backup = await this._contentProvider.backup(this.resource, token);
if (token.isCancellationRequested) {
return {};
}
const stats = await this._resolveStats(this.resource);
return {
meta: {
mtime: stats?.mtime ?? Date.now(),
viewType: this.notebook.viewType,
backupId
}
};
if (backup instanceof VSBuffer) {
return {
content: bufferToReadable(backup)
};
} else {
return {
meta: {
mtime: stats?.mtime ?? Date.now(),
viewType: this.notebook.viewType,
backupId: backup
}
};
}
}
async revert(options?: IRevertOptions | undefined): Promise<void> {
@ -208,7 +215,7 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook
}
this._logService.debug('[notebook editor model] load from provider', this.resource.toString());
await this._loadFromProvider(backup?.meta?.backupId);
await this._loadFromProvider(backup);
assertType(this.isResolved());
return this;
}
@ -225,14 +232,21 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook
return untitledDocumentData;
}
private async _loadFromProvider(backupId: string | undefined): Promise<void> {
private async _loadFromProvider(backup: IResolvedWorkingCopyBackup<NotebookDocumentBackupData> | undefined): Promise<void> {
const untitledData = await this.getUntitledDocumentData(this.resource);
// If we're loading untitled file data we should ensure the model is dirty
if (untitledData) {
this._onDidChangeDirty.fire();
}
const data = await this._contentProvider.open(this.resource, backupId, untitledData, CancellationToken.None);
const data = await this._contentProvider.open(this.resource,
backup?.meta?.backupId ?? (
backup?.value
? await streamToBuffer(backup?.value)
: undefined
),
untitledData, CancellationToken.None
);
this._lastResolvedFileStat = await this._resolveStats(this.resource);
@ -278,7 +292,7 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook
this.notebook.reset(data.data.cells, data.data.metadata, data.transientOptions);
}
if (backupId) {
if (backup) {
this._workingCopyBackupService.discardBackup(this._workingCopyIdentifier);
this.setDirty(true);
} else {

View file

@ -13,7 +13,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
type NotebookSelector = string | glob.IRelativePattern | INotebookExclusiveDocumentFilter;
export interface NotebookEditorDescriptor {
readonly extension: ExtensionIdentifier,
readonly extension?: ExtensionIdentifier,
readonly id: string;
readonly displayName: string;
readonly selectors: readonly { filenamePattern?: string; excludeFileNamePattern?: string; }[];
@ -24,7 +24,7 @@ export interface NotebookEditorDescriptor {
export class NotebookProviderInfo {
readonly extension: ExtensionIdentifier;
readonly extension?: ExtensionIdentifier;
readonly id: string;
readonly displayName: string;
readonly priority: RegisteredEditorPriority;

View file

@ -21,10 +21,10 @@ export const INotebookService = createDecorator<INotebookService>('notebookServi
export interface INotebookContentProvider {
options: TransientOptions;
open(uri: URI, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<{ data: NotebookData, transientOptions: TransientOptions; }>;
open(uri: URI, backupId: string | VSBuffer | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<{ data: NotebookData, 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>;
backup(uri: URI, token: CancellationToken): Promise<string | VSBuffer>;
}
export interface INotebookSerializer {