Skip to content

Commit 7ae360e

Browse files
authored
Merge pull request #10619 from microsoft/ds/custom_editor
Merge ds/custom_editor into master
2 parents c3e6ef7 + b699559 commit 7ae360e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+923
-370
lines changed

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"version": "2020.4.0-dev",
66
"languageServerVersion": "0.5.30",
77
"publisher": "ms-python",
8+
"enableProposedApi": false,
89
"author": {
910
"name": "Microsoft Corporation"
1011
},
@@ -24,7 +25,7 @@
2425
"theme": "dark"
2526
},
2627
"engines": {
27-
"vscode": "^1.42.0"
28+
"vscode": "^1.43.0"
2829
},
2930
"keywords": [
3031
"python",
@@ -83,7 +84,8 @@
8384
"onCommand:python.datascience.exportfileandoutputasnotebook",
8485
"onCommand:python.datascience.selectJupyterInterpreter",
8586
"onCommand:python.datascience.selectjupytercommandline",
86-
"onCommand:python.enableSourceMapSupport"
87+
"onCommand:python.enableSourceMapSupport",
88+
"onCustomEditor:NativeEditorProvider.ipynb"
8789
],
8890
"main": "./out/client/extension",
8991
"contributes": {

src/client/common/application/customEditorService.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as vscode from 'vscode';
66

77
import { UseCustomEditorApi } from '../constants';
88
import { noop } from '../utils/misc';
9-
import { ICommandManager, ICustomEditorService, WebviewCustomEditorProvider } from './types';
9+
import { CustomEditorProvider, ICommandManager, ICustomEditorService } from './types';
1010

1111
@injectable()
1212
export class CustomEditorService implements ICustomEditorService {
@@ -15,14 +15,14 @@ export class CustomEditorService implements ICustomEditorService {
1515
@inject(UseCustomEditorApi) private readonly useCustomEditorApi: boolean
1616
) {}
1717

18-
public registerWebviewCustomEditorProvider(
18+
public registerCustomEditorProvider(
1919
viewType: string,
20-
provider: WebviewCustomEditorProvider,
20+
provider: CustomEditorProvider,
2121
options?: vscode.WebviewPanelOptions
2222
): vscode.Disposable {
2323
if (this.useCustomEditorApi) {
2424
// tslint:disable-next-line: no-any
25-
return (vscode.window as any).registerWebviewCustomEditorProvider(viewType, provider, options);
25+
return (vscode.window as any).registerCustomEditorProvider(viewType, provider, options);
2626
} else {
2727
return { dispose: noop };
2828
}

src/client/common/application/types.ts

Lines changed: 188 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,86 +1096,243 @@ export interface IActiveResourceService {
10961096
}
10971097

10981098
// Temporary hack to get the nyc compiler to find these types. vscode.proposed.d.ts doesn't work for some reason.
1099+
//#region Custom editors: https://github.com/microsoft/vscode/issues/77131
1100+
1101+
// tslint:disable: interface-name
10991102
/**
1100-
* Defines the editing functionality of a webview editor. This allows the webview editor to hook into standard
1103+
* Defines the editing capability of a custom webview editor. This allows the webview editor to hook into standard
11011104
* editor events such as `undo` or `save`.
11021105
*
1103-
* @param EditType Type of edits. Edit objects must be json serializable.
1106+
* @param EditType Type of edits.
11041107
*/
1105-
// tslint:disable-next-line: interface-name
1106-
export interface WebviewCustomEditorEditingDelegate<EditType> {
1108+
export interface CustomEditorEditingDelegate<EditType = unknown> {
11071109
/**
11081110
* Event triggered by extensions to signal to VS Code that an edit has occurred.
11091111
*/
1110-
readonly onEdit: Event<{ readonly resource: Uri; readonly edit: EditType }>;
1112+
readonly onDidEdit: Event<CustomDocumentEditEvent<EditType>>;
11111113
/**
1112-
* Save a resource.
1114+
* Save the resource.
11131115
*
1114-
* @param resource Resource being saved.
1116+
* @param document Document to save.
1117+
* @param cancellation Token that signals the save is no longer required (for example, if another save was triggered).
11151118
*
11161119
* @return Thenable signaling that the save has completed.
11171120
*/
1118-
save(resource: Uri): Thenable<void>;
1121+
save(document: CustomDocument, cancellation: CancellationToken): Thenable<void>;
11191122

11201123
/**
1121-
* Save an existing resource at a new path.
1124+
* Save the existing resource at a new path.
11221125
*
1123-
* @param resource Resource being saved.
1126+
* @param document Document to save.
11241127
* @param targetResource Location to save to.
11251128
*
11261129
* @return Thenable signaling that the save has completed.
11271130
*/
1128-
saveAs(resource: Uri, targetResource: Uri): Thenable<void>;
1131+
saveAs(document: CustomDocument, targetResource: Uri): Thenable<void>;
11291132

11301133
/**
11311134
* Apply a set of edits.
11321135
*
1133-
* Note that is not invoked when `onEdit` is called as `onEdit` implies also updating the view to reflect the edit.
1136+
* Note that is not invoked when `onDidEdit` is called because `onDidEdit` implies also updating the view to reflect the edit.
11341137
*
1135-
* @param resource Resource being edited.
1138+
* @param document Document to apply edits to.
11361139
* @param edit Array of edits. Sorted from oldest to most recent.
11371140
*
11381141
* @return Thenable signaling that the change has completed.
11391142
*/
1140-
applyEdits(resource: Uri, edits: readonly EditType[]): Thenable<void>;
1143+
applyEdits(document: CustomDocument, edits: readonly EditType[]): Thenable<void>;
11411144

11421145
/**
11431146
* Undo a set of edits.
11441147
*
1145-
* This is triggered when a user undoes an edit or when revert is called on a file.
1148+
* This is triggered when a user undoes an edit.
11461149
*
1147-
* @param resource Resource being edited.
1150+
* @param document Document to undo edits from.
11481151
* @param edit Array of edits. Sorted from most recent to oldest.
11491152
*
11501153
* @return Thenable signaling that the change has completed.
11511154
*/
1152-
undoEdits(resource: Uri, edits: readonly EditType[]): Thenable<void>;
1155+
undoEdits(document: CustomDocument, edits: readonly EditType[]): Thenable<void>;
1156+
1157+
/**
1158+
* Revert the file to its last saved state.
1159+
*
1160+
* @param document Document to revert.
1161+
* @param edits Added or applied edits.
1162+
*
1163+
* @return Thenable signaling that the change has completed.
1164+
*/
1165+
revert(document: CustomDocument, edits: CustomDocumentRevert<EditType>): Thenable<void>;
1166+
1167+
/**
1168+
* Back up the resource in its current state.
1169+
*
1170+
* Backups are used for hot exit and to prevent data loss. Your `backup` method should persist the resource in
1171+
* its current state, i.e. with the edits applied. Most commonly this means saving the resource to disk in
1172+
* the `ExtensionContext.storagePath`. When VS Code reloads and your custom editor is opened for a resource,
1173+
* your extension should first check to see if any backups exist for the resource. If there is a backup, your
1174+
* extension should load the file contents from there instead of from the resource in the workspace.
1175+
*
1176+
* `backup` is triggered whenever an edit it made. Calls to `backup` are debounced so that if multiple edits are
1177+
* made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when
1178+
* `auto save` is enabled (since auto save already persists resource ).
1179+
*
1180+
* @param document Document to revert.
1181+
* @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your
1182+
* extension to decided how to respond to cancellation. If for example your extension is backing up a large file
1183+
* in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather
1184+
* than cancelling it to ensure that VS Code has some valid backup.
1185+
*/
1186+
backup(document: CustomDocument, cancellation: CancellationToken): Thenable<void>;
11531187
}
11541188

1155-
// tslint:disable-next-line: interface-name
1156-
export interface WebviewCustomEditorProvider {
1189+
/**
1190+
* Event triggered by extensions to signal to VS Code that an edit has occurred on a CustomDocument``.
1191+
*/
1192+
export interface CustomDocumentEditEvent<EditType = unknown> {
1193+
/**
1194+
* Document the edit is for.
1195+
*/
1196+
readonly document: CustomDocument;
1197+
11571198
/**
1158-
* Controls the editing functionality of a webview editor. This allows the webview editor to hook into standard
1159-
* editor events such as `undo` or `save`.
1199+
* Object that describes the edit.
11601200
*
1161-
* WebviewEditors that do not have `editingCapability` are considered to be readonly. Users can still interact
1162-
* with readonly editors, but these editors will not integrate with VS Code's standard editor functionality.
1201+
* Edit objects are passed back to your extension in `undoEdits`, `applyEdits`, and `revert`.
11631202
*/
1164-
readonly editingDelegate?: WebviewCustomEditorEditingDelegate<unknown>;
1203+
readonly edit: EditType;
1204+
1205+
/**
1206+
* Display name describing the edit.
1207+
*/
1208+
readonly label?: string;
1209+
}
1210+
1211+
/**
1212+
* Data about a revert for a `CustomDocument`.
1213+
*/
1214+
export interface CustomDocumentRevert<EditType = unknown> {
1215+
/**
1216+
* List of edits that were undone to get the document back to its on disk state.
1217+
*/
1218+
readonly undoneEdits: readonly EditType[];
1219+
1220+
/**
1221+
* List of edits that were reapplied to get the document back to its on disk state.
1222+
*/
1223+
readonly appliedEdits: readonly EditType[];
1224+
}
1225+
1226+
/**
1227+
* Represents a custom document used by a `CustomEditorProvider`.
1228+
*
1229+
* Custom documents are only used within a given `CustomEditorProvider`. The lifecycle of a
1230+
* `CustomDocument` is managed by VS Code. When no more references remain to a given `CustomDocument`,
1231+
* then it is disposed of.
1232+
*
1233+
* @param UserDataType Type of custom object that extensions can store on the document.
1234+
*/
1235+
export interface CustomDocument<UserDataType = unknown> {
1236+
/**
1237+
* The associated viewType for this document.
1238+
*/
1239+
readonly viewType: string;
1240+
1241+
/**
1242+
* The associated uri for this document.
1243+
*/
1244+
readonly uri: Uri;
1245+
1246+
/**
1247+
* Event fired when there are no more references to the `CustomDocument`.
1248+
*/
1249+
readonly onDidDispose: Event<void>;
1250+
1251+
/**
1252+
* Custom data that an extension can store on the document.
1253+
*/
1254+
userData?: UserDataType;
1255+
}
1256+
1257+
/**
1258+
* Provider for webview editors that use a custom data model.
1259+
*
1260+
* Custom webview editors use [`CustomDocument`](#CustomDocument) as their data model.
1261+
* This gives extensions full control over actions such as edit, save, and backup.
1262+
*
1263+
* You should use custom text based editors when dealing with binary files or more complex scenarios. For simple text
1264+
* based documents, use [`WebviewTextEditorProvider`](#WebviewTextEditorProvider) instead.
1265+
*/
1266+
export interface CustomEditorProvider {
1267+
/**
1268+
* Defines the editing capability of a custom webview document.
1269+
*
1270+
* When not provided, the document is considered readonly.
1271+
*/
1272+
readonly editingDelegate?: CustomEditorEditingDelegate;
1273+
/**
1274+
* Resolve the model for a given resource.
1275+
*
1276+
* `resolveCustomDocument` is called when the first editor for a given resource is opened, and the resolve document
1277+
* is passed to `resolveCustomEditor`. The resolved `CustomDocument` is re-used for subsequent editor opens.
1278+
* If all editors for a given resource are closed, the `CustomDocument` is disposed of. Opening an editor at
1279+
* this point will trigger another call to `resolveCustomDocument`.
1280+
*
1281+
* @param document Document to resolve.
1282+
*
1283+
* @return The capabilities of the resolved document.
1284+
*/
1285+
resolveCustomDocument(document: CustomDocument): Thenable<void>;
1286+
11651287
/**
11661288
* Resolve a webview editor for a given resource.
11671289
*
1168-
* To resolve a webview editor, a provider must fill in its initial html content and hook up all
1169-
* the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`.
1290+
* This is called when a user first opens a resource for a `CustomTextEditorProvider`, or if they reopen an
1291+
* existing editor using this `CustomTextEditorProvider`.
1292+
*
1293+
* To resolve a webview editor, the provider must fill in its initial html content and hook up all
1294+
* the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later,
1295+
* for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details
11701296
*
1171-
* @param resource Resource being resolved.
1172-
* @param webview Webview being resolved. The provider should take ownership of this webview.
1297+
* @param document Document for the resource being resolved.
1298+
* @param webviewPanel Webview to resolve.
11731299
*
11741300
* @return Thenable indicating that the webview editor has been resolved.
11751301
*/
1176-
resolveWebviewEditor(resource: Uri, webview: WebviewPanel): Thenable<void>;
1302+
resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel): Thenable<void>;
11771303
}
11781304

1305+
/**
1306+
* Provider for text based webview editors.
1307+
*
1308+
* Text based webview editors use a [`TextDocument`](#TextDocument) as their data model. This considerably simplifies
1309+
* implementing a webview editor as it allows VS Code to handle many common operations such as
1310+
* undo and backup. The provider is responsible for synchronizing text changes between the webview and the `TextDocument`.
1311+
*
1312+
* You should use text based webview editors when dealing with text based file formats, such as `xml` or `json`.
1313+
* For binary files or more specialized use cases, see [CustomEditorProvider](#CustomEditorProvider).
1314+
*/
1315+
export interface CustomTextEditorProvider {
1316+
/**
1317+
* Resolve a webview editor for a given text resource.
1318+
*
1319+
* This is called when a user first opens a resource for a `CustomTextEditorProvider`, or if they reopen an
1320+
* existing editor using this `CustomTextEditorProvider`.
1321+
*
1322+
* To resolve a webview editor, the provider must fill in its initial html content and hook up all
1323+
* the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later,
1324+
* for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details.
1325+
*
1326+
* @param document Document for the resource to resolve.
1327+
* @param webviewPanel Webview to resolve.
1328+
*
1329+
* @return Thenable indicating that the webview editor has been resolved.
1330+
*/
1331+
resolveCustomTextEditor(document: TextDocument, webviewPanel: WebviewPanel): Thenable<void>;
1332+
}
1333+
1334+
//#endregion
1335+
11791336
export const ICustomEditorService = Symbol('ICustomEditorService');
11801337
export interface ICustomEditorService {
11811338
/**
@@ -1187,9 +1344,9 @@ export interface ICustomEditorService {
11871344
*
11881345
* @return Disposable that unregisters the `WebviewCustomEditorProvider`.
11891346
*/
1190-
registerWebviewCustomEditorProvider(
1347+
registerCustomEditorProvider(
11911348
viewType: string,
1192-
provider: WebviewCustomEditorProvider,
1349+
provider: CustomEditorProvider,
11931350
options?: WebviewPanelOptions
11941351
): Disposable;
11951352
/**

src/client/common/insidersBuild/insidersExtensionService.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ export class InsidersExtensionService implements IExtensionSingleActivationServi
8585
* @returns `true` if install channel is handled in these miscellaneous cases, `false` if install channel needs further handling
8686
*/
8787
public async handleEdgeCases(installChannel: ExtensionChannels, isDefault: boolean): Promise<boolean> {
88-
if (await this.promptToInstallInsidersIfApplicable(isDefault)) {
88+
// When running UI Tests we might want to disable these prompts.
89+
if (process.env.UITEST_DISABLE_INSIDERS) {
90+
return true;
91+
} else if (await this.promptToInstallInsidersIfApplicable(isDefault)) {
8992
return true;
9093
} else if (await this.setInsidersChannelToOffIfApplicable(installChannel)) {
9194
return true;

src/client/common/serviceRegistry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ import {
106106
import { IMultiStepInputFactory, MultiStepInputFactory } from './utils/multiStepInput';
107107
import { Random } from './utils/random';
108108

109+
// tslint:disable-next-line: max-func-body-length
109110
export function registerTypes(serviceManager: IServiceManager) {
110111
serviceManager.addSingletonInstance<boolean>(IsWindows, IS_WINDOWS);
111112

src/client/datascience/interactive-common/interactiveBase.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import {
8989
IMessageCell,
9090
INotebook,
9191
INotebookExporter,
92+
INotebookProvider,
9293
INotebookServer,
9394
INotebookServerOptions,
9495
InterruptResult,
@@ -152,7 +153,8 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
152153
@unmanaged() title: string,
153154
@unmanaged() viewColumn: ViewColumn,
154155
@unmanaged() experimentsManager: IExperimentsManager,
155-
@unmanaged() private switcher: KernelSwitcher
156+
@unmanaged() private switcher: KernelSwitcher,
157+
@unmanaged() private readonly notebookProvider: INotebookProvider
156158
) {
157159
super(
158160
configuration,
@@ -326,13 +328,6 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
326328
super.dispose();
327329
this.listeners.forEach(l => l.dispose());
328330
this.updateContexts(undefined);
329-
330-
// When closing an editor, dispose of the notebook associated with it.
331-
// This won't work when we have multiple views of the notebook though. Notebook ownership
332-
// should probably move to whatever owns the backing model.
333-
return this.notebook?.dispose().then(() => {
334-
this._notebook = undefined;
335-
});
336331
}
337332

338333
public startProgress() {
@@ -1093,7 +1088,7 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
10931088
this.getNotebookOptions()
10941089
]);
10951090
try {
1096-
notebook = uri ? await server.createNotebook(resource, uri, options?.metadata) : undefined;
1091+
notebook = uri ? await this.notebookProvider.getNotebook(server, uri, options?.metadata) : undefined;
10971092
} catch (e) {
10981093
// If we get an invalid kernel error, make sure to ask the user to switch
10991094
if (e instanceof JupyterInvalidKernelError && server && server.getConnectionInfo()?.localLaunch) {

0 commit comments

Comments
 (0)