Skip to content

Commit 8ec699a

Browse files
Kartik Rajeleanorjboyd
Kartik Raj
authored andcommitted
Use saveEditor proposed API for running untitled Python files (microsoft#21183)
Closes microsoft#21182
1 parent a740f95 commit 8ec699a

File tree

6 files changed

+72
-18
lines changed

6 files changed

+72
-18
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"envShellEvent",
2424
"testObserver",
2525
"quickPickItemTooltip",
26-
"envCollectionWorkspace"
26+
"envCollectionWorkspace",
27+
"saveEditor"
2728
],
2829
"author": {
2930
"name": "Microsoft Corporation"

src/client/common/application/types.ts

+10
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,16 @@ export interface IWorkspaceService {
851851
* @return A promise that resolves to a {@link TextDocument document}.
852852
*/
853853
openTextDocument(options?: { language?: string; content?: string }): Thenable<TextDocument>;
854+
/**
855+
* Saves the editor identified by the given resource to a new file name as provided by the user and
856+
* returns the resulting resource or `undefined` if save was not successful or cancelled.
857+
*
858+
* **Note** that an editor with the provided resource must be opened in order to be saved as.
859+
*
860+
* @param uri the associated uri for the opened editor to save as.
861+
* @return A thenable that resolves when the save-as operation has finished.
862+
*/
863+
saveAs(uri: Uri): Thenable<Uri | undefined>;
854864
}
855865

856866
export const ITerminalManager = Symbol('ITerminalManager');

src/client/common/application/workspace.ts

+10
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,14 @@ export class WorkspaceService implements IWorkspaceService {
112112
const enabledSearchExcludes = Object.keys(searchExcludes).filter((key) => searchExcludes.get(key) === true);
113113
return `{${enabledSearchExcludes.join(',')}}`;
114114
}
115+
116+
public async saveAs(uri: Uri): Promise<Uri | undefined> {
117+
try {
118+
// This is a proposed API hence putting it inside try...catch.
119+
const result = await workspace.saveAs(uri);
120+
return result;
121+
} catch (ex) {
122+
return undefined;
123+
}
124+
}
115125
}

src/client/terminals/codeExecution/helper.ts

+3-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import '../../common/extensions';
55
import { inject, injectable } from 'inversify';
66
import { l10n, Position, Range, TextEditor, Uri } from 'vscode';
77

8-
import { IApplicationShell, ICommandManager, IDocumentManager } from '../../common/application/types';
8+
import { IApplicationShell, IDocumentManager, IWorkspaceService } from '../../common/application/types';
99
import { PYTHON_LANGUAGE } from '../../common/constants';
1010
import * as internalScripts from '../../common/process/internal/scripts';
1111
import { IProcessServiceFactory } from '../../common/process/types';
@@ -123,12 +123,8 @@ export class CodeExecutionHelper implements ICodeExecutionHelper {
123123
public async saveFileIfDirty(file: Uri): Promise<Resource> {
124124
const docs = this.documentManager.textDocuments.filter((d) => d.uri.path === file.path);
125125
if (docs.length === 1 && docs[0].isDirty) {
126-
const deferred = createDeferred<Uri>();
127-
this.documentManager.onDidSaveTextDocument((e) => deferred.resolve(e.uri));
128-
const commandManager = this.serviceContainer.get<ICommandManager>(ICommandManager);
129-
await commandManager.executeCommand('workbench.action.files.save', file);
130-
const savedFileUri = await deferred.promise;
131-
return savedFileUri;
126+
const workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
127+
return workspaceService.saveAs(docs[0].uri);
132128
}
133129
return undefined;
134130
}

src/test/terminals/codeExecution/helper.test.ts

+13-10
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ import * as fs from 'fs-extra';
88
import * as path from 'path';
99
import { SemVer } from 'semver';
1010
import * as TypeMoq from 'typemoq';
11-
import { EventEmitter, Position, Range, Selection, TextDocument, TextEditor, TextLine, Uri } from 'vscode';
12-
import { IApplicationShell, ICommandManager, IDocumentManager } from '../../../client/common/application/types';
11+
import { Position, Range, Selection, TextDocument, TextEditor, TextLine, Uri } from 'vscode';
12+
import {
13+
IApplicationShell,
14+
ICommandManager,
15+
IDocumentManager,
16+
IWorkspaceService,
17+
} from '../../../client/common/application/types';
1318
import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../../client/common/constants';
1419
import '../../../client/common/extensions';
1520
import { ProcessService } from '../../../client/common/process/proc';
@@ -38,6 +43,7 @@ suite('Terminal - Code Execution Helper', () => {
3843
let processService: TypeMoq.IMock<IProcessService>;
3944
let interpreterService: TypeMoq.IMock<IInterpreterService>;
4045
let commandManager: TypeMoq.IMock<ICommandManager>;
46+
let workspaceService: TypeMoq.IMock<IWorkspaceService>;
4147
const workingPython: PythonEnvironment = {
4248
path: PYTHON_PATH,
4349
version: new SemVer('3.6.6-final'),
@@ -51,6 +57,7 @@ suite('Terminal - Code Execution Helper', () => {
5157
setup(() => {
5258
const serviceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
5359
commandManager = TypeMoq.Mock.ofType<ICommandManager>();
60+
workspaceService = TypeMoq.Mock.ofType<IWorkspaceService>();
5461
documentManager = TypeMoq.Mock.ofType<IDocumentManager>();
5562
applicationShell = TypeMoq.Mock.ofType<IApplicationShell>();
5663
const envVariablesProvider = TypeMoq.Mock.ofType<IEnvironmentVariablesProvider>();
@@ -69,6 +76,9 @@ suite('Terminal - Code Execution Helper', () => {
6976
envVariablesProvider
7077
.setup((e) => e.getEnvironmentVariables(TypeMoq.It.isAny()))
7178
.returns(() => Promise.resolve({}));
79+
serviceContainer
80+
.setup((c) => c.get(TypeMoq.It.isValue(IWorkspaceService)))
81+
.returns(() => workspaceService.object);
7282
serviceContainer
7383
.setup((c) => c.get(TypeMoq.It.isValue(IProcessServiceFactory), TypeMoq.It.isAny()))
7484
.returns(() => processServiceFactory.object);
@@ -367,20 +377,13 @@ suite('Terminal - Code Execution Helper', () => {
367377
.setup((d) => d.textDocuments)
368378
.returns(() => [document.object])
369379
.verifiable(TypeMoq.Times.once());
370-
const saveEmitter = new EventEmitter<TextDocument>();
371-
documentManager.setup((d) => d.onDidSaveTextDocument).returns(() => saveEmitter.event);
372380
document.setup((doc) => doc.isUntitled).returns(() => true);
373381
document.setup((doc) => doc.isDirty).returns(() => true);
374382
document.setup((doc) => doc.languageId).returns(() => PYTHON_LANGUAGE);
375383
const untitledUri = Uri.file('Untitled-1');
376384
document.setup((doc) => doc.uri).returns(() => untitledUri);
377-
const savedDocument = TypeMoq.Mock.ofType<TextDocument>();
378385
const expectedSavedUri = Uri.file('one.py');
379-
savedDocument.setup((doc) => doc.uri).returns(() => expectedSavedUri);
380-
commandManager
381-
.setup((c) => c.executeCommand('workbench.action.files.save', untitledUri))
382-
.callback(() => saveEmitter.fire(savedDocument.object))
383-
.returns(() => Promise.resolve());
386+
workspaceService.setup((w) => w.saveAs(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedSavedUri));
384387

385388
const savedUri = await helper.saveFileIfDirty(untitledUri);
386389

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
// https://github.com/microsoft/vscode/issues/178713
7+
8+
declare module 'vscode' {
9+
10+
export namespace workspace {
11+
12+
/**
13+
* Saves the editor identified by the given resource and returns the resulting resource or `undefined`
14+
* if save was not successful.
15+
*
16+
* **Note** that an editor with the provided resource must be opened in order to be saved.
17+
*
18+
* @param uri the associated uri for the opened editor to save.
19+
* @return A thenable that resolves when the save operation has finished.
20+
*/
21+
export function save(uri: Uri): Thenable<Uri | undefined>;
22+
23+
/**
24+
* Saves the editor identified by the given resource to a new file name as provided by the user and
25+
* returns the resulting resource or `undefined` if save was not successful or cancelled.
26+
*
27+
* **Note** that an editor with the provided resource must be opened in order to be saved as.
28+
*
29+
* @param uri the associated uri for the opened editor to save as.
30+
* @return A thenable that resolves when the save-as operation has finished.
31+
*/
32+
export function saveAs(uri: Uri): Thenable<Uri | undefined>;
33+
}
34+
}

0 commit comments

Comments
 (0)