Skip to content

Commit e98f64f

Browse files
authored
Merge pull request microsoft#99488 from microsoft/rebornix/genericCustomEditorInputFactory
Handle backup data with custom editor input factory
2 parents 31101e8 + 71ae22a commit e98f64f

9 files changed

Lines changed: 127 additions & 49 deletions

File tree

src/vs/base/common/network.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export namespace Schemas {
5656

5757
export const vscodeCustomEditor = 'vscode-custom-editor';
5858

59+
export const vscodeNotebook = 'vscode-notebook';
60+
5961
export const vscodeSettings = 'vscode-settings';
6062

6163
export const webviewPanel = 'webview-panel';

src/vs/workbench/common/editor.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ export interface IFileEditorInputFactory {
176176

177177
interface ICustomEditorInputFactory {
178178
createCustomEditorInput(resource: URI, instantiationService: IInstantiationService): Promise<IEditorInput>;
179+
canResolveBackup(editorInput: IEditorInput, backupResource: URI): boolean;
179180
}
180181

181182
export interface IEditorInputFactoryRegistry {
@@ -193,12 +194,12 @@ export interface IEditorInputFactoryRegistry {
193194
/**
194195
* Registers the custom editor input factory to use for custom inputs.
195196
*/
196-
registerCustomEditorInputFactory(factory: ICustomEditorInputFactory): void;
197+
registerCustomEditorInputFactory(scheme: string, factory: ICustomEditorInputFactory): void;
197198

198199
/**
199200
* Returns the custom editor input factory to use for custom inputs.
200201
*/
201-
getCustomEditorInputFactory(): ICustomEditorInputFactory;
202+
getCustomEditorInputFactory(scheme: string): ICustomEditorInputFactory | undefined;
202203

203204
/**
204205
* Registers a editor input factory for the given editor input to the registry. An editor input factory
@@ -1280,7 +1281,7 @@ export interface IEditorMemento<T> {
12801281
class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry {
12811282
private instantiationService: IInstantiationService | undefined;
12821283
private fileEditorInputFactory: IFileEditorInputFactory | undefined;
1283-
private customEditorInputFactory: ICustomEditorInputFactory | undefined;
1284+
private customEditorInputFactoryInstances: Map<string, ICustomEditorInputFactory> = new Map();
12841285

12851286
private readonly editorInputFactoryConstructors: Map<string, IConstructorSignature0<IEditorInputFactory>> = new Map();
12861287
private readonly editorInputFactoryInstances: Map<string, IEditorInputFactory> = new Map();
@@ -1308,12 +1309,12 @@ class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry {
13081309
return assertIsDefined(this.fileEditorInputFactory);
13091310
}
13101311

1311-
registerCustomEditorInputFactory(factory: ICustomEditorInputFactory): void {
1312-
this.customEditorInputFactory = factory;
1312+
registerCustomEditorInputFactory(scheme: string, factory: ICustomEditorInputFactory): void {
1313+
this.customEditorInputFactoryInstances.set(scheme, factory);
13131314
}
13141315

1315-
getCustomEditorInputFactory(): ICustomEditorInputFactory {
1316-
return assertIsDefined(this.customEditorInputFactory);
1316+
getCustomEditorInputFactory(scheme: string): ICustomEditorInputFactory | undefined {
1317+
return this.customEditorInputFactoryInstances.get(scheme);
13171318
}
13181319

13191320
registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): IDisposable {

src/vs/workbench/contrib/backup/common/backupRestorer.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ export class BackupRestorer implements IWorkbenchContribution {
7171

7272
private findEditorByResource(resource: URI): IEditorInput | undefined {
7373
for (const editor of this.editorService.editors) {
74-
if (isEqual(editor.resource, resource)) {
74+
const customFactory = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getCustomEditorInputFactory(resource.scheme);
75+
if (customFactory && customFactory.canResolveBackup(editor, resource)) {
76+
return editor;
77+
} else if (isEqual(editor.resource, resource)) {
7578
return editor;
7679
}
7780
}
@@ -99,9 +102,10 @@ export class BackupRestorer implements IWorkbenchContribution {
99102

100103
// handle custom editors by asking the custom editor input factory
101104
// to create the input.
102-
if (resource.scheme === Schemas.vscodeCustomEditor) {
103-
const editor = await Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getCustomEditorInputFactory()
104-
.createCustomEditorInput(resource, this.instantiationService);
105+
const customFactory = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getCustomEditorInputFactory(resource.scheme);
106+
107+
if (customFactory) {
108+
const editor = await customFactory.createCustomEditorInput(resource, this.instantiationService);
105109
return { editor, options };
106110
}
107111

src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { Schemas } from 'vs/base/common/network';
67
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
78
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
89
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
@@ -38,4 +39,4 @@ Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactor
3839
CustomEditorInputFactory);
3940

4041
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories)
41-
.registerCustomEditorInputFactory(CustomEditorInputFactory);
42+
.registerCustomEditorInputFactory(Schemas.vscodeCustomEditor, CustomEditorInputFactory);

src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { IWebviewService, WebviewExtensionDescription } from 'vs/workbench/contr
1212
import { reviveWebviewExtensionDescription, SerializedWebview, WebviewEditorInputFactory, DeserializedWebview } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory';
1313
import { IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService';
1414
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
15+
import { isEqual } from 'vs/base/common/resources';
16+
import { Schemas } from 'vs/base/common/network';
1517

1618
export interface CustomDocumentBackupData {
1719
readonly viewType: string;
@@ -124,4 +126,14 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory {
124126
return editor;
125127
});
126128
}
129+
130+
public static canResolveBackup(editorInput: IEditorInput, backupResource: URI): boolean {
131+
if (editorInput instanceof CustomEditorInput) {
132+
if (editorInput.resource.path === backupResource.path && backupResource.authority === editorInput.viewType) {
133+
return true;
134+
}
135+
}
136+
137+
return false;
138+
}
127139
}

src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { coalesce, distinct } from 'vs/base/common/arrays';
7+
import { Schemas } from 'vs/base/common/network';
68
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
79
import { ResourceMap } from 'vs/base/common/map';
810
import { parse } from 'vs/base/common/marshalling';
@@ -24,17 +26,17 @@ import { Registry } from 'vs/platform/registry/common/platform';
2426
import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor';
2527
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
2628
import { EditorInput, Extensions as EditorInputExtensions, IEditorInput, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor';
29+
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
2730
import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor';
2831
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
2932
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
3033
import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl';
31-
import { CellKind, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon';
34+
import { CellKind, CellUri, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon';
3235
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
3336
import { IEditorGroup, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
3437
import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService';
3538
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
3639
import { CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/common/editorAssociationsSetting';
37-
import { coalesce, distinct } from 'vs/base/common/arrays';
3840
import { CustomEditorInfo } from 'vs/workbench/contrib/customEditor/common/customEditor';
3941
import { NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
4042
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
@@ -70,42 +72,73 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
7072
]
7173
);
7274

73-
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(
74-
NotebookEditorInput.ID,
75-
class implements IEditorInputFactory {
76-
canSerialize(): boolean {
77-
return true;
75+
class NotebookEditorFactory implements IEditorInputFactory {
76+
canSerialize(): boolean {
77+
return true;
78+
}
79+
serialize(input: EditorInput): string {
80+
assertType(input instanceof NotebookEditorInput);
81+
return JSON.stringify({
82+
resource: input.resource,
83+
name: input.name,
84+
viewType: input.viewType,
85+
group: input.group
86+
});
87+
}
88+
deserialize(instantiationService: IInstantiationService, raw: string) {
89+
type Data = { resource: URI, name: string, viewType: string, group: number };
90+
const data = <Data>parse(raw);
91+
if (!data) {
92+
return undefined;
7893
}
79-
serialize(input: EditorInput): string {
80-
assertType(input instanceof NotebookEditorInput);
81-
return JSON.stringify({
82-
resource: input.resource,
83-
name: input.name,
84-
viewType: input.viewType,
85-
group: input.group
86-
});
94+
const { resource, name, viewType } = data;
95+
if (!data || !URI.isUri(resource) || typeof name !== 'string' || typeof viewType !== 'string') {
96+
return undefined;
8797
}
88-
deserialize(instantiationService: IInstantiationService, raw: string) {
89-
type Data = { resource: URI, name: string, viewType: string, group: number };
90-
const data = <Data>parse(raw);
91-
if (!data) {
92-
return undefined;
93-
}
94-
const { resource, name, viewType } = data;
95-
if (!data || !URI.isUri(resource) || typeof name !== 'string' || typeof viewType !== 'string') {
96-
return undefined;
97-
}
9898

99-
// if we have two editors open with the same resource (in different editor groups), we should then create two different
100-
// editor inputs, instead of `getOrCreate`.
101-
const input = NotebookEditorInput.create(instantiationService, resource, name, viewType);
102-
if (typeof data.group === 'number') {
103-
input.updateGroup(data.group);
99+
// if we have two editors open with the same resource (in different editor groups), we should then create two different
100+
// editor inputs, instead of `getOrCreate`.
101+
const input = NotebookEditorInput.create(instantiationService, resource, name, viewType);
102+
if (typeof data.group === 'number') {
103+
input.updateGroup(data.group);
104+
}
105+
106+
return input;
107+
}
108+
109+
static async createCustomEditorInput(resource: URI, instantiationService: IInstantiationService): Promise<NotebookEditorInput> {
110+
return instantiationService.invokeFunction(async accessor => {
111+
const backupFileService = accessor.get<IBackupFileService>(IBackupFileService);
112+
113+
const backup = await backupFileService.resolve<NotebookDocumentBackupData>(resource);
114+
if (!backup?.meta) {
115+
throw new Error(`No backup found for Notebook editor: ${resource}`);
104116
}
105117

118+
const input = NotebookEditorInput.create(instantiationService, resource, backup.meta.name, backup.meta.viewType, { startDirty: true });
106119
return input;
120+
});
121+
}
122+
123+
static canResolveBackup(editorInput: IEditorInput, backupResource: URI): boolean {
124+
if (editorInput instanceof NotebookEditorInput) {
125+
if (isEqual(editorInput.resource.with({ scheme: Schemas.vscodeNotebook }), backupResource)) {
126+
return true;
127+
}
107128
}
129+
130+
return false;
108131
}
132+
}
133+
134+
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(
135+
NotebookEditorInput.ID,
136+
NotebookEditorFactory
137+
);
138+
139+
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerCustomEditorInputFactory(
140+
Schemas.vscodeNotebook,
141+
NotebookEditorFactory
109142
);
110143

111144
function getFirstNotebookInfo(notebookService: INotebookService, uri: URI): NotebookProviderInfo | undefined {
@@ -227,7 +260,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri
227260
// Create a copy of the input.
228261
// Unlike normal editor inputs, we do not want to share custom editor inputs
229262
// between multiple editors / groups.
230-
const copiedInput = this.instantiationService.createInstance(NotebookEditorInput, originalInput.resource, originalInput.name, originalInput.viewType);
263+
const copiedInput = NotebookEditorInput.create(this.instantiationService, originalInput.resource, originalInput.name, originalInput.viewType);
231264
copiedInput.updateGroup(group.id);
232265

233266
if (context === OpenEditorContext.MOVE_EDITOR) {

src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebo
1313
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
1414

1515
let NOTEBOOK_EDITOR_INPUT_HANDLE = 0;
16+
17+
interface NotebookEditorInputOptions {
18+
startDirty?: boolean;
19+
}
20+
1621
export class NotebookEditorInput extends EditorInput {
17-
static create(instantiationService: IInstantiationService, resource: URI, name: string, viewType: string | undefined) {
18-
return instantiationService.createInstance(NotebookEditorInput, resource, name, viewType);
22+
static create(instantiationService: IInstantiationService, resource: URI, name: string, viewType: string | undefined, options: NotebookEditorInputOptions = {}) {
23+
return instantiationService.createInstance(NotebookEditorInput, resource, name, viewType, options);
1924
}
2025

2126
static readonly ID: string = 'workbench.input.notebook';
@@ -31,18 +36,22 @@ export class NotebookEditorInput extends EditorInput {
3136
this._group = group;
3237
}
3338

39+
private _defaultDirtyState: boolean = false;
40+
3441
readonly id: number = NOTEBOOK_EDITOR_INPUT_HANDLE++;
3542
constructor(
3643
public resource: URI,
3744
public name: string,
3845
public readonly viewType: string | undefined,
46+
public readonly options: NotebookEditorInputOptions,
3947
@INotebookService private readonly notebookService: INotebookService,
4048
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
4149
@IFileDialogService private readonly fileDialogService: IFileDialogService,
4250
// @IEditorService private readonly editorService: IEditorService,
4351
@IInstantiationService private readonly instantiationService: IInstantiationService
4452
) {
4553
super();
54+
this._defaultDirtyState = !!options.startDirty;
4655
}
4756

4857
getTypeId(): string {
@@ -54,6 +63,10 @@ export class NotebookEditorInput extends EditorInput {
5463
}
5564

5665
isDirty() {
66+
if (!this.textModel) {
67+
return !!this._defaultDirtyState;
68+
}
69+
5770
return this.textModel?.isDirty() || false;
5871
}
5972

src/vs/workbench/contrib/notebook/common/notebookCommon.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,11 @@ export interface INotebookTextModelBackup {
560560
cells: ICellDto2[]
561561
}
562562

563+
export interface NotebookDocumentBackupData {
564+
readonly viewType: string;
565+
readonly name: string;
566+
}
567+
563568
export interface IEditor extends editorCommon.ICompositeCodeEditor {
564569
readonly onDidChangeModel: Event<NotebookTextModel | undefined>;
565570
readonly onDidFocusEditorWidget: Event<void>;

src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { EditorModel, IRevertOptions } from 'vs/workbench/common/editor';
77
import { Emitter, Event } from 'vs/base/common/event';
8-
import { INotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
8+
import { INotebookEditorModel, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon';
99
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
1010
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
1111
import { ResourceMap } from 'vs/base/common/map';
@@ -17,6 +17,7 @@ import { basename } from 'vs/base/common/resources';
1717
import { CancellationTokenSource } from 'vs/base/common/cancellation';
1818
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
1919
import { DefaultEndOfLine, ITextBuffer, EndOfLinePreference } from 'vs/editor/common/model';
20+
import { Schemas } from 'vs/base/common/network';
2021

2122
export interface INotebookEditorModelManager {
2223
models: NotebookEditorModel[];
@@ -66,7 +67,7 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
6667
super();
6768

6869
const input = this;
69-
this._workingCopyResource = resource.with({ scheme: 'vscode-notebook' });
70+
this._workingCopyResource = resource.with({ scheme: Schemas.vscodeNotebook });
7071
const workingCopyAdapter = new class implements IWorkingCopy {
7172
readonly resource = input._workingCopyResource;
7273
get name() { return input.name; }
@@ -84,8 +85,14 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
8485

8586
capabilities = 0;
8687

87-
async backup(): Promise<IWorkingCopyBackup> {
88-
return { content: this._notebook.createSnapshot(true) };
88+
async backup(): Promise<IWorkingCopyBackup<NotebookDocumentBackupData>> {
89+
return {
90+
meta: {
91+
name: this._name,
92+
viewType: this._notebook.viewType
93+
},
94+
content: this._notebook.createSnapshot(true)
95+
};
8996
}
9097

9198
async revert(options?: IRevertOptions | undefined): Promise<void> {

0 commit comments

Comments
 (0)