diff --git a/package.json b/package.json index fd6ddcea84ca..0d64b360e43b 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "envShellEvent", "testObserver", "quickPickItemTooltip", - "envCollectionWorkspace" + "envCollectionWorkspace", + "saveEditor" ], "author": { "name": "Microsoft Corporation" diff --git a/src/client/common/application/types.ts b/src/client/common/application/types.ts index 77d7b5af3279..e57bac656d19 100644 --- a/src/client/common/application/types.ts +++ b/src/client/common/application/types.ts @@ -851,6 +851,16 @@ export interface IWorkspaceService { * @return A promise that resolves to a {@link TextDocument document}. */ openTextDocument(options?: { language?: string; content?: string }): Thenable; + /** + * Saves the editor identified by the given resource to a new file name as provided by the user and + * returns the resulting resource or `undefined` if save was not successful or cancelled. + * + * **Note** that an editor with the provided resource must be opened in order to be saved as. + * + * @param uri the associated uri for the opened editor to save as. + * @return A thenable that resolves when the save-as operation has finished. + */ + saveAs(uri: Uri): Thenable; } export const ITerminalManager = Symbol('ITerminalManager'); diff --git a/src/client/common/application/workspace.ts b/src/client/common/application/workspace.ts index 0a5fd8d81816..11ed98cf0076 100644 --- a/src/client/common/application/workspace.ts +++ b/src/client/common/application/workspace.ts @@ -112,4 +112,14 @@ export class WorkspaceService implements IWorkspaceService { const enabledSearchExcludes = Object.keys(searchExcludes).filter((key) => searchExcludes.get(key) === true); return `{${enabledSearchExcludes.join(',')}}`; } + + public async saveAs(uri: Uri): Promise { + try { + // This is a proposed API hence putting it inside try...catch. + const result = await workspace.saveAs(uri); + return result; + } catch (ex) { + return undefined; + } + } } diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index eee7d91a0db2..0a1ae9f0be06 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -5,7 +5,7 @@ import '../../common/extensions'; import { inject, injectable } from 'inversify'; import { l10n, Position, Range, TextEditor, Uri } from 'vscode'; -import { IApplicationShell, ICommandManager, IDocumentManager } from '../../common/application/types'; +import { IApplicationShell, IDocumentManager, IWorkspaceService } from '../../common/application/types'; import { PYTHON_LANGUAGE } from '../../common/constants'; import * as internalScripts from '../../common/process/internal/scripts'; import { IProcessServiceFactory } from '../../common/process/types'; @@ -123,12 +123,8 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { public async saveFileIfDirty(file: Uri): Promise { const docs = this.documentManager.textDocuments.filter((d) => d.uri.path === file.path); if (docs.length === 1 && docs[0].isDirty) { - const deferred = createDeferred(); - this.documentManager.onDidSaveTextDocument((e) => deferred.resolve(e.uri)); - const commandManager = this.serviceContainer.get(ICommandManager); - await commandManager.executeCommand('workbench.action.files.save', file); - const savedFileUri = await deferred.promise; - return savedFileUri; + const workspaceService = this.serviceContainer.get(IWorkspaceService); + return workspaceService.saveAs(docs[0].uri); } return undefined; } diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 57bf51883eb8..684ca22b6c75 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -8,8 +8,13 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; -import { EventEmitter, Position, Range, Selection, TextDocument, TextEditor, TextLine, Uri } from 'vscode'; -import { IApplicationShell, ICommandManager, IDocumentManager } from '../../../client/common/application/types'; +import { Position, Range, Selection, TextDocument, TextEditor, TextLine, Uri } from 'vscode'; +import { + IApplicationShell, + ICommandManager, + IDocumentManager, + IWorkspaceService, +} from '../../../client/common/application/types'; import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../../client/common/constants'; import '../../../client/common/extensions'; import { ProcessService } from '../../../client/common/process/proc'; @@ -38,6 +43,7 @@ suite('Terminal - Code Execution Helper', () => { let processService: TypeMoq.IMock; let interpreterService: TypeMoq.IMock; let commandManager: TypeMoq.IMock; + let workspaceService: TypeMoq.IMock; const workingPython: PythonEnvironment = { path: PYTHON_PATH, version: new SemVer('3.6.6-final'), @@ -51,6 +57,7 @@ suite('Terminal - Code Execution Helper', () => { setup(() => { const serviceContainer = TypeMoq.Mock.ofType(); commandManager = TypeMoq.Mock.ofType(); + workspaceService = TypeMoq.Mock.ofType(); documentManager = TypeMoq.Mock.ofType(); applicationShell = TypeMoq.Mock.ofType(); const envVariablesProvider = TypeMoq.Mock.ofType(); @@ -69,6 +76,9 @@ suite('Terminal - Code Execution Helper', () => { envVariablesProvider .setup((e) => e.getEnvironmentVariables(TypeMoq.It.isAny())) .returns(() => Promise.resolve({})); + serviceContainer + .setup((c) => c.get(TypeMoq.It.isValue(IWorkspaceService))) + .returns(() => workspaceService.object); serviceContainer .setup((c) => c.get(TypeMoq.It.isValue(IProcessServiceFactory), TypeMoq.It.isAny())) .returns(() => processServiceFactory.object); @@ -367,20 +377,13 @@ suite('Terminal - Code Execution Helper', () => { .setup((d) => d.textDocuments) .returns(() => [document.object]) .verifiable(TypeMoq.Times.once()); - const saveEmitter = new EventEmitter(); - documentManager.setup((d) => d.onDidSaveTextDocument).returns(() => saveEmitter.event); document.setup((doc) => doc.isUntitled).returns(() => true); document.setup((doc) => doc.isDirty).returns(() => true); document.setup((doc) => doc.languageId).returns(() => PYTHON_LANGUAGE); const untitledUri = Uri.file('Untitled-1'); document.setup((doc) => doc.uri).returns(() => untitledUri); - const savedDocument = TypeMoq.Mock.ofType(); const expectedSavedUri = Uri.file('one.py'); - savedDocument.setup((doc) => doc.uri).returns(() => expectedSavedUri); - commandManager - .setup((c) => c.executeCommand('workbench.action.files.save', untitledUri)) - .callback(() => saveEmitter.fire(savedDocument.object)) - .returns(() => Promise.resolve()); + workspaceService.setup((w) => w.saveAs(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedSavedUri)); const savedUri = await helper.saveFileIfDirty(untitledUri); diff --git a/typings/vscode-proposed/vscode.proposed.saveEditor.d.ts b/typings/vscode-proposed/vscode.proposed.saveEditor.d.ts new file mode 100644 index 000000000000..9088939a4649 --- /dev/null +++ b/typings/vscode-proposed/vscode.proposed.saveEditor.d.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// https://github.com/microsoft/vscode/issues/178713 + +declare module 'vscode' { + + export namespace workspace { + + /** + * Saves the editor identified by the given resource and returns the resulting resource or `undefined` + * if save was not successful. + * + * **Note** that an editor with the provided resource must be opened in order to be saved. + * + * @param uri the associated uri for the opened editor to save. + * @return A thenable that resolves when the save operation has finished. + */ + export function save(uri: Uri): Thenable; + + /** + * Saves the editor identified by the given resource to a new file name as provided by the user and + * returns the resulting resource or `undefined` if save was not successful or cancelled. + * + * **Note** that an editor with the provided resource must be opened in order to be saved as. + * + * @param uri the associated uri for the opened editor to save as. + * @return A thenable that resolves when the save-as operation has finished. + */ + export function saveAs(uri: Uri): Thenable; + } +}