Skip to content
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

Use saveEditor proposed API for running untitled Python files #21183

Merged
merged 1 commit into from
May 4, 2023
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"envShellEvent",
"testObserver",
"quickPickItemTooltip",
"envCollectionWorkspace"
"envCollectionWorkspace",
"saveEditor"
],
"author": {
"name": "Microsoft Corporation"
Expand Down
10 changes: 10 additions & 0 deletions src/client/common/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,16 @@ export interface IWorkspaceService {
* @return A promise that resolves to a {@link TextDocument document}.
*/
openTextDocument(options?: { language?: string; content?: string }): Thenable<TextDocument>;
/**
* 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<Uri | undefined>;
}

export const ITerminalManager = Symbol('ITerminalManager');
Expand Down
10 changes: 10 additions & 0 deletions src/client/common/application/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Uri | undefined> {
try {
// This is a proposed API hence putting it inside try...catch.
const result = await workspace.saveAs(uri);
return result;
} catch (ex) {
return undefined;
}
}
}
10 changes: 3 additions & 7 deletions src/client/terminals/codeExecution/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -123,12 +123,8 @@ export class CodeExecutionHelper implements ICodeExecutionHelper {
public async saveFileIfDirty(file: Uri): Promise<Resource> {
const docs = this.documentManager.textDocuments.filter((d) => d.uri.path === file.path);
if (docs.length === 1 && docs[0].isDirty) {
const deferred = createDeferred<Uri>();
this.documentManager.onDidSaveTextDocument((e) => deferred.resolve(e.uri));
const commandManager = this.serviceContainer.get<ICommandManager>(ICommandManager);
await commandManager.executeCommand('workbench.action.files.save', file);
const savedFileUri = await deferred.promise;
return savedFileUri;
const workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
return workspaceService.saveAs(docs[0].uri);
}
return undefined;
}
Expand Down
23 changes: 13 additions & 10 deletions src/test/terminals/codeExecution/helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -38,6 +43,7 @@ suite('Terminal - Code Execution Helper', () => {
let processService: TypeMoq.IMock<IProcessService>;
let interpreterService: TypeMoq.IMock<IInterpreterService>;
let commandManager: TypeMoq.IMock<ICommandManager>;
let workspaceService: TypeMoq.IMock<IWorkspaceService>;
const workingPython: PythonEnvironment = {
path: PYTHON_PATH,
version: new SemVer('3.6.6-final'),
Expand All @@ -51,6 +57,7 @@ suite('Terminal - Code Execution Helper', () => {
setup(() => {
const serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
commandManager = TypeMoq.Mock.ofType<ICommandManager>();
workspaceService = TypeMoq.Mock.ofType<IWorkspaceService>();
documentManager = TypeMoq.Mock.ofType<IDocumentManager>();
applicationShell = TypeMoq.Mock.ofType<IApplicationShell>();
const envVariablesProvider = TypeMoq.Mock.ofType<IEnvironmentVariablesProvider>();
Expand All @@ -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);
Expand Down Expand Up @@ -367,20 +377,13 @@ suite('Terminal - Code Execution Helper', () => {
.setup((d) => d.textDocuments)
.returns(() => [document.object])
.verifiable(TypeMoq.Times.once());
const saveEmitter = new EventEmitter<TextDocument>();
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<TextDocument>();
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);

Expand Down
34 changes: 34 additions & 0 deletions typings/vscode-proposed/vscode.proposed.saveEditor.d.ts
Original file line number Diff line number Diff line change
@@ -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<Uri | undefined>;

/**
* 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<Uri | undefined>;
}
}