Skip to content

Merge ds/custom_editor into master #10619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Mar 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"version": "2020.4.0-dev",
"languageServerVersion": "0.5.30",
"publisher": "ms-python",
"enableProposedApi": false,
"author": {
"name": "Microsoft Corporation"
},
Expand All @@ -24,7 +25,7 @@
"theme": "dark"
},
"engines": {
"vscode": "^1.42.0"
"vscode": "^1.43.0"
},
"keywords": [
"python",
Expand Down Expand Up @@ -83,7 +84,8 @@
"onCommand:python.datascience.exportfileandoutputasnotebook",
"onCommand:python.datascience.selectJupyterInterpreter",
"onCommand:python.datascience.selectjupytercommandline",
"onCommand:python.enableSourceMapSupport"
"onCommand:python.enableSourceMapSupport",
"onCustomEditor:NativeEditorProvider.ipynb"
],
"main": "./out/client/extension",
"contributes": {
Expand Down
8 changes: 4 additions & 4 deletions src/client/common/application/customEditorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as vscode from 'vscode';

import { UseCustomEditorApi } from '../constants';
import { noop } from '../utils/misc';
import { ICommandManager, ICustomEditorService, WebviewCustomEditorProvider } from './types';
import { CustomEditorProvider, ICommandManager, ICustomEditorService } from './types';

@injectable()
export class CustomEditorService implements ICustomEditorService {
Expand All @@ -15,14 +15,14 @@ export class CustomEditorService implements ICustomEditorService {
@inject(UseCustomEditorApi) private readonly useCustomEditorApi: boolean
) {}

public registerWebviewCustomEditorProvider(
public registerCustomEditorProvider(
viewType: string,
provider: WebviewCustomEditorProvider,
provider: CustomEditorProvider,
options?: vscode.WebviewPanelOptions
): vscode.Disposable {
if (this.useCustomEditorApi) {
// tslint:disable-next-line: no-any
return (vscode.window as any).registerWebviewCustomEditorProvider(viewType, provider, options);
return (vscode.window as any).registerCustomEditorProvider(viewType, provider, options);
} else {
return { dispose: noop };
}
Expand Down
219 changes: 188 additions & 31 deletions src/client/common/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1096,86 +1096,243 @@ export interface IActiveResourceService {
}

// Temporary hack to get the nyc compiler to find these types. vscode.proposed.d.ts doesn't work for some reason.
//#region Custom editors: https://github.com/microsoft/vscode/issues/77131

// tslint:disable: interface-name
/**
* Defines the editing functionality of a webview editor. This allows the webview editor to hook into standard
* Defines the editing capability of a custom webview editor. This allows the webview editor to hook into standard
* editor events such as `undo` or `save`.
*
* @param EditType Type of edits. Edit objects must be json serializable.
* @param EditType Type of edits.
*/
// tslint:disable-next-line: interface-name
export interface WebviewCustomEditorEditingDelegate<EditType> {
export interface CustomEditorEditingDelegate<EditType = unknown> {
/**
* Event triggered by extensions to signal to VS Code that an edit has occurred.
*/
readonly onEdit: Event<{ readonly resource: Uri; readonly edit: EditType }>;
readonly onDidEdit: Event<CustomDocumentEditEvent<EditType>>;
/**
* Save a resource.
* Save the resource.
*
* @param resource Resource being saved.
* @param document Document to save.
* @param cancellation Token that signals the save is no longer required (for example, if another save was triggered).
*
* @return Thenable signaling that the save has completed.
*/
save(resource: Uri): Thenable<void>;
save(document: CustomDocument, cancellation: CancellationToken): Thenable<void>;

/**
* Save an existing resource at a new path.
* Save the existing resource at a new path.
*
* @param resource Resource being saved.
* @param document Document to save.
* @param targetResource Location to save to.
*
* @return Thenable signaling that the save has completed.
*/
saveAs(resource: Uri, targetResource: Uri): Thenable<void>;
saveAs(document: CustomDocument, targetResource: Uri): Thenable<void>;

/**
* Apply a set of edits.
*
* Note that is not invoked when `onEdit` is called as `onEdit` implies also updating the view to reflect the edit.
* Note that is not invoked when `onDidEdit` is called because `onDidEdit` implies also updating the view to reflect the edit.
*
* @param resource Resource being edited.
* @param document Document to apply edits to.
* @param edit Array of edits. Sorted from oldest to most recent.
*
* @return Thenable signaling that the change has completed.
*/
applyEdits(resource: Uri, edits: readonly EditType[]): Thenable<void>;
applyEdits(document: CustomDocument, edits: readonly EditType[]): Thenable<void>;

/**
* Undo a set of edits.
*
* This is triggered when a user undoes an edit or when revert is called on a file.
* This is triggered when a user undoes an edit.
*
* @param resource Resource being edited.
* @param document Document to undo edits from.
* @param edit Array of edits. Sorted from most recent to oldest.
*
* @return Thenable signaling that the change has completed.
*/
undoEdits(resource: Uri, edits: readonly EditType[]): Thenable<void>;
undoEdits(document: CustomDocument, edits: readonly EditType[]): Thenable<void>;

/**
* Revert the file to its last saved state.
*
* @param document Document to revert.
* @param edits Added or applied edits.
*
* @return Thenable signaling that the change has completed.
*/
revert(document: CustomDocument, edits: CustomDocumentRevert<EditType>): Thenable<void>;

/**
* Back up the resource in its current state.
*
* Backups are used for hot exit and to prevent data loss. Your `backup` method should persist the resource in
* its current state, i.e. with the edits applied. Most commonly this means saving the resource to disk in
* the `ExtensionContext.storagePath`. When VS Code reloads and your custom editor is opened for a resource,
* your extension should first check to see if any backups exist for the resource. If there is a backup, your
* extension should load the file contents from there instead of from the resource in the workspace.
*
* `backup` is triggered whenever an edit it made. Calls to `backup` are debounced so that if multiple edits are
* made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when
* `auto save` is enabled (since auto save already persists resource ).
*
* @param document Document to revert.
* @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your
* extension to decided how to respond to cancellation. If for example your extension is backing up a large file
* in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather
* than cancelling it to ensure that VS Code has some valid backup.
*/
backup(document: CustomDocument, cancellation: CancellationToken): Thenable<void>;
}

// tslint:disable-next-line: interface-name
export interface WebviewCustomEditorProvider {
/**
* Event triggered by extensions to signal to VS Code that an edit has occurred on a CustomDocument``.
*/
export interface CustomDocumentEditEvent<EditType = unknown> {
/**
* Document the edit is for.
*/
readonly document: CustomDocument;

/**
* Controls the editing functionality of a webview editor. This allows the webview editor to hook into standard
* editor events such as `undo` or `save`.
* Object that describes the edit.
*
* WebviewEditors that do not have `editingCapability` are considered to be readonly. Users can still interact
* with readonly editors, but these editors will not integrate with VS Code's standard editor functionality.
* Edit objects are passed back to your extension in `undoEdits`, `applyEdits`, and `revert`.
*/
readonly editingDelegate?: WebviewCustomEditorEditingDelegate<unknown>;
readonly edit: EditType;

/**
* Display name describing the edit.
*/
readonly label?: string;
}

/**
* Data about a revert for a `CustomDocument`.
*/
export interface CustomDocumentRevert<EditType = unknown> {
/**
* List of edits that were undone to get the document back to its on disk state.
*/
readonly undoneEdits: readonly EditType[];

/**
* List of edits that were reapplied to get the document back to its on disk state.
*/
readonly appliedEdits: readonly EditType[];
}

/**
* Represents a custom document used by a `CustomEditorProvider`.
*
* Custom documents are only used within a given `CustomEditorProvider`. The lifecycle of a
* `CustomDocument` is managed by VS Code. When no more references remain to a given `CustomDocument`,
* then it is disposed of.
*
* @param UserDataType Type of custom object that extensions can store on the document.
*/
export interface CustomDocument<UserDataType = unknown> {
/**
* The associated viewType for this document.
*/
readonly viewType: string;

/**
* The associated uri for this document.
*/
readonly uri: Uri;

/**
* Event fired when there are no more references to the `CustomDocument`.
*/
readonly onDidDispose: Event<void>;

/**
* Custom data that an extension can store on the document.
*/
userData?: UserDataType;
}

/**
* Provider for webview editors that use a custom data model.
*
* Custom webview editors use [`CustomDocument`](#CustomDocument) as their data model.
* This gives extensions full control over actions such as edit, save, and backup.
*
* You should use custom text based editors when dealing with binary files or more complex scenarios. For simple text
* based documents, use [`WebviewTextEditorProvider`](#WebviewTextEditorProvider) instead.
*/
export interface CustomEditorProvider {
/**
* Defines the editing capability of a custom webview document.
*
* When not provided, the document is considered readonly.
*/
readonly editingDelegate?: CustomEditorEditingDelegate;
/**
* Resolve the model for a given resource.
*
* `resolveCustomDocument` is called when the first editor for a given resource is opened, and the resolve document
* is passed to `resolveCustomEditor`. The resolved `CustomDocument` is re-used for subsequent editor opens.
* If all editors for a given resource are closed, the `CustomDocument` is disposed of. Opening an editor at
* this point will trigger another call to `resolveCustomDocument`.
*
* @param document Document to resolve.
*
* @return The capabilities of the resolved document.
*/
resolveCustomDocument(document: CustomDocument): Thenable<void>;

/**
* Resolve a webview editor for a given resource.
*
* To resolve a webview editor, a provider must fill in its initial html content and hook up all
* the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`.
* This is called when a user first opens a resource for a `CustomTextEditorProvider`, or if they reopen an
* existing editor using this `CustomTextEditorProvider`.
*
* To resolve a webview editor, the provider must fill in its initial html content and hook up all
* the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later,
* for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details
*
* @param resource Resource being resolved.
* @param webview Webview being resolved. The provider should take ownership of this webview.
* @param document Document for the resource being resolved.
* @param webviewPanel Webview to resolve.
*
* @return Thenable indicating that the webview editor has been resolved.
*/
resolveWebviewEditor(resource: Uri, webview: WebviewPanel): Thenable<void>;
resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel): Thenable<void>;
}

/**
* Provider for text based webview editors.
*
* Text based webview editors use a [`TextDocument`](#TextDocument) as their data model. This considerably simplifies
* implementing a webview editor as it allows VS Code to handle many common operations such as
* undo and backup. The provider is responsible for synchronizing text changes between the webview and the `TextDocument`.
*
* You should use text based webview editors when dealing with text based file formats, such as `xml` or `json`.
* For binary files or more specialized use cases, see [CustomEditorProvider](#CustomEditorProvider).
*/
export interface CustomTextEditorProvider {
/**
* Resolve a webview editor for a given text resource.
*
* This is called when a user first opens a resource for a `CustomTextEditorProvider`, or if they reopen an
* existing editor using this `CustomTextEditorProvider`.
*
* To resolve a webview editor, the provider must fill in its initial html content and hook up all
* the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later,
* for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details.
*
* @param document Document for the resource to resolve.
* @param webviewPanel Webview to resolve.
*
* @return Thenable indicating that the webview editor has been resolved.
*/
resolveCustomTextEditor(document: TextDocument, webviewPanel: WebviewPanel): Thenable<void>;
}

//#endregion

export const ICustomEditorService = Symbol('ICustomEditorService');
export interface ICustomEditorService {
/**
Expand All @@ -1187,9 +1344,9 @@ export interface ICustomEditorService {
*
* @return Disposable that unregisters the `WebviewCustomEditorProvider`.
*/
registerWebviewCustomEditorProvider(
registerCustomEditorProvider(
viewType: string,
provider: WebviewCustomEditorProvider,
provider: CustomEditorProvider,
options?: WebviewPanelOptions
): Disposable;
/**
Expand Down
5 changes: 4 additions & 1 deletion src/client/common/insidersBuild/insidersExtensionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ export class InsidersExtensionService implements IExtensionSingleActivationServi
* @returns `true` if install channel is handled in these miscellaneous cases, `false` if install channel needs further handling
*/
public async handleEdgeCases(installChannel: ExtensionChannels, isDefault: boolean): Promise<boolean> {
if (await this.promptToInstallInsidersIfApplicable(isDefault)) {
// When running UI Tests we might want to disable these prompts.
if (process.env.UITEST_DISABLE_INSIDERS) {
return true;
} else if (await this.promptToInstallInsidersIfApplicable(isDefault)) {
return true;
} else if (await this.setInsidersChannelToOffIfApplicable(installChannel)) {
return true;
Expand Down
1 change: 1 addition & 0 deletions src/client/common/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import {
import { IMultiStepInputFactory, MultiStepInputFactory } from './utils/multiStepInput';
import { Random } from './utils/random';

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

Expand Down
13 changes: 4 additions & 9 deletions src/client/datascience/interactive-common/interactiveBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import {
IMessageCell,
INotebook,
INotebookExporter,
INotebookProvider,
INotebookServer,
INotebookServerOptions,
InterruptResult,
Expand Down Expand Up @@ -152,7 +153,8 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
@unmanaged() title: string,
@unmanaged() viewColumn: ViewColumn,
@unmanaged() experimentsManager: IExperimentsManager,
@unmanaged() private switcher: KernelSwitcher
@unmanaged() private switcher: KernelSwitcher,
@unmanaged() private readonly notebookProvider: INotebookProvider
) {
super(
configuration,
Expand Down Expand Up @@ -326,13 +328,6 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
super.dispose();
this.listeners.forEach(l => l.dispose());
this.updateContexts(undefined);

// When closing an editor, dispose of the notebook associated with it.
// This won't work when we have multiple views of the notebook though. Notebook ownership
// should probably move to whatever owns the backing model.
return this.notebook?.dispose().then(() => {
this._notebook = undefined;
});
}

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