Skip to content

Commit 4f1f696

Browse files
authored
Merge branch 'main' into don/issue15987
2 parents f5ab705 + 6b784e5 commit 4f1f696

File tree

8 files changed

+171
-90
lines changed

8 files changed

+171
-90
lines changed

.github/release_plan.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ Feature freeze is Monday @ 17:00 America/Vancouver, XXX XX. At that point, commi
2424
</details>
2525

2626

27-
# Release candidate (Monday, XXX XX)
27+
# Release candidate (Thursday, XXX XX)
28+
NOTE: This Thursday occurs during TESTING week. Branching should be done during this week to freeze the release with only the correct changes. Any last minute fixes go in as candidates into the release branch and will require team approval.
2829

30+
Other:
2931
NOTE: Third Party Notices are automatically added by our build pipelines using https://tools.opensource.microsoft.com/notice.
3032
NOTE: the number of this release is in the issue title and can be substituted in wherever you see [YYYY.minor].
3133

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "python",
33
"displayName": "Python",
44
"description": "Python language support with extension access points for IntelliSense (Pylance), Debugging (Python Debugger), linting, formatting, refactoring, unit tests, and more.",
5-
"version": "2024.23.0-dev",
5+
"version": "2025.1.0-dev",
66
"featureFlags": {
77
"usingNewInterpreterStorage": true
88
},

src/client/common/terminal/service.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { useEnvExtension } from '../../envExt/api.internal';
2525
import { ensureTerminalLegacy } from '../../envExt/api.legacy';
2626
import { sleep } from '../utils/async';
2727
import { isWindows } from '../utils/platform';
28+
import { getPythonMinorVersion } from '../../repl/replUtils';
2829

2930
@injectable()
3031
export class TerminalService implements ITerminalService, Disposable {
@@ -108,7 +109,15 @@ export class TerminalService implements ITerminalService, Disposable {
108109

109110
const config = getConfiguration('python');
110111
const pythonrcSetting = config.get<boolean>('terminal.shellIntegration.enabled');
111-
if ((isPythonShell && !pythonrcSetting) || (isPythonShell && isWindows())) {
112+
113+
const minorVersion = this.options?.resource
114+
? await getPythonMinorVersion(
115+
this.options.resource,
116+
this.serviceContainer.get<IInterpreterService>(IInterpreterService),
117+
)
118+
: undefined;
119+
120+
if ((isPythonShell && !pythonrcSetting) || (isPythonShell && isWindows()) || (minorVersion ?? 0) >= 13) {
112121
// If user has explicitly disabled SI for Python, use sendText for inside Terminal REPL.
113122
terminal.sendText(commandLine);
114123
return undefined;

src/client/repl/replUtils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,17 @@ export function getTabNameForUri(uri: Uri): string | undefined {
118118

119119
return undefined;
120120
}
121+
122+
/**
123+
* Function that will return the minor version of current active Python interpreter.
124+
*/
125+
export async function getPythonMinorVersion(
126+
uri: Uri | undefined,
127+
interpreterService: IInterpreterService,
128+
): Promise<number | undefined> {
129+
if (uri) {
130+
const pythonVersion = await getActiveInterpreter(uri, interpreterService);
131+
return pythonVersion?.version?.minor;
132+
}
133+
return undefined;
134+
}

src/test/common/terminals/service.unit.test.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,29 @@ import {
1111
TerminalShellExecution,
1212
TerminalShellExecutionEndEvent,
1313
TerminalShellIntegration,
14+
Uri,
1415
Terminal as VSCodeTerminal,
1516
WorkspaceConfiguration,
1617
} from 'vscode';
1718
import { ITerminalManager, IWorkspaceService } from '../../../client/common/application/types';
1819
import { EXTENSION_ROOT_DIR } from '../../../client/common/constants';
1920
import { IPlatformService } from '../../../client/common/platform/types';
2021
import { TerminalService } from '../../../client/common/terminal/service';
21-
import { ITerminalActivator, ITerminalHelper, TerminalShellType } from '../../../client/common/terminal/types';
22+
import {
23+
ITerminalActivator,
24+
ITerminalHelper,
25+
TerminalCreationOptions,
26+
TerminalShellType,
27+
} from '../../../client/common/terminal/types';
2228
import { IDisposableRegistry } from '../../../client/common/types';
2329
import { IServiceContainer } from '../../../client/ioc/types';
2430
import { ITerminalAutoActivation } from '../../../client/terminals/types';
2531
import { createPythonInterpreter } from '../../utils/interpreters';
2632
import * as workspaceApis from '../../../client/common/vscodeApis/workspaceApis';
2733
import * as platform from '../../../client/common/utils/platform';
2834
import * as extapi from '../../../client/envExt/api.internal';
35+
import { IInterpreterService } from '../../../client/interpreter/contracts';
36+
import { PythonEnvironment } from '../../../client/pythonEnvironments/info';
2937

3038
suite('Terminal Service', () => {
3139
let service: TerminalService;
@@ -46,6 +54,8 @@ suite('Terminal Service', () => {
4654
let editorConfig: TypeMoq.IMock<WorkspaceConfiguration>;
4755
let isWindowsStub: sinon.SinonStub;
4856
let useEnvExtensionStub: sinon.SinonStub;
57+
let interpreterService: TypeMoq.IMock<IInterpreterService>;
58+
let options: TypeMoq.IMock<TerminalCreationOptions>;
4959

5060
setup(() => {
5161
useEnvExtensionStub = sinon.stub(extapi, 'useEnvExtension');
@@ -92,6 +102,13 @@ suite('Terminal Service', () => {
92102
disposables = [];
93103

94104
mockServiceContainer = TypeMoq.Mock.ofType<IServiceContainer>();
105+
interpreterService = TypeMoq.Mock.ofType<IInterpreterService>();
106+
interpreterService
107+
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
108+
.returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment));
109+
110+
options = TypeMoq.Mock.ofType<TerminalCreationOptions>();
111+
options.setup((o) => o.resource).returns(() => Uri.parse('a'));
95112

96113
mockServiceContainer.setup((c) => c.get(ITerminalManager)).returns(() => terminalManager.object);
97114
mockServiceContainer.setup((c) => c.get(ITerminalHelper)).returns(() => terminalHelper.object);
@@ -100,6 +117,7 @@ suite('Terminal Service', () => {
100117
mockServiceContainer.setup((c) => c.get(IWorkspaceService)).returns(() => workspaceService.object);
101118
mockServiceContainer.setup((c) => c.get(ITerminalActivator)).returns(() => terminalActivator.object);
102119
mockServiceContainer.setup((c) => c.get(ITerminalAutoActivation)).returns(() => terminalAutoActivator.object);
120+
mockServiceContainer.setup((c) => c.get(IInterpreterService)).returns(() => interpreterService.object);
103121
getConfigurationStub = sinon.stub(workspaceApis, 'getConfiguration');
104122
isWindowsStub = sinon.stub(platform, 'isWindows');
105123
pythonConfig = TypeMoq.Mock.ofType<WorkspaceConfiguration>();
@@ -117,6 +135,7 @@ suite('Terminal Service', () => {
117135
}
118136
disposables.filter((item) => !!item).forEach((item) => item.dispose());
119137
sinon.restore();
138+
interpreterService.reset();
120139
});
121140

122141
test('Ensure terminal is disposed', async () => {
@@ -239,7 +258,7 @@ suite('Terminal Service', () => {
239258
terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1));
240259
});
241260

242-
test('Ensure sendText is NOT called when Python shell integration and terminal shell integration are both enabled - Mac, Linux', async () => {
261+
test('Ensure sendText is NOT called when Python shell integration and terminal shell integration are both enabled - Mac, Linux && Python < 3.13', async () => {
243262
isWindowsStub.returns(false);
244263
pythonConfig
245264
.setup((p) => p.get('terminal.shellIntegration.enabled'))
@@ -261,6 +280,36 @@ suite('Terminal Service', () => {
261280
terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.never());
262281
});
263282

283+
test('Ensure sendText is called when Python shell integration and terminal shell integration are both enabled - Mac, Linux && Python >= 3.13', async () => {
284+
interpreterService.reset();
285+
286+
interpreterService
287+
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
288+
.returns(() =>
289+
Promise.resolve({ path: 'yo', version: { major: 3, minor: 13, patch: 0 } } as PythonEnvironment),
290+
);
291+
292+
isWindowsStub.returns(false);
293+
pythonConfig
294+
.setup((p) => p.get('terminal.shellIntegration.enabled'))
295+
.returns(() => true)
296+
.verifiable(TypeMoq.Times.once());
297+
298+
terminalHelper
299+
.setup((helper) => helper.getEnvironmentActivationCommands(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
300+
.returns(() => Promise.resolve(undefined));
301+
302+
service = new TerminalService(mockServiceContainer.object, options.object);
303+
const textToSend = 'Some Text';
304+
terminalHelper.setup((h) => h.identifyTerminalShell(TypeMoq.It.isAny())).returns(() => TerminalShellType.bash);
305+
terminalManager.setup((t) => t.createTerminal(TypeMoq.It.isAny())).returns(() => terminal.object);
306+
307+
await service.ensureTerminal();
308+
await service.executeCommand(textToSend, true);
309+
310+
terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.once());
311+
});
312+
264313
test('Ensure sendText IS called even when Python shell integration and terminal shell integration are both enabled - Window', async () => {
265314
isWindowsStub.returns(true);
266315
pythonConfig

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

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ import { IServiceContainer } from '../../../client/ioc/types';
3333
import { EnvironmentType, PythonEnvironment } from '../../../client/pythonEnvironments/info';
3434
import { CodeExecutionHelper } from '../../../client/terminals/codeExecution/helper';
3535
import { ICodeExecutionHelper } from '../../../client/terminals/types';
36-
import { PYTHON_PATH } from '../../common';
36+
import { PYTHON_PATH, getPythonSemVer } from '../../common';
3737
import { ReplType } from '../../../client/repl/types';
3838

3939
const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'python_files', 'terminalExec');
4040

41-
suite('Terminal - Code Execution Helper', () => {
41+
suite('Terminal - Code Execution Helper', async () => {
4242
let activeResourceService: TypeMoq.IMock<IActiveResourceService>;
4343
let documentManager: TypeMoq.IMock<IDocumentManager>;
4444
let applicationShell: TypeMoq.IMock<IApplicationShell>;
@@ -234,25 +234,28 @@ suite('Terminal - Code Execution Helper', () => {
234234
expect(normalizedCode).to.be.equal(normalizedExpected);
235235
}
236236

237-
['', '1', '2', '3', '4', '5', '6', '7', '8'].forEach((fileNameSuffix) => {
238-
test(`Ensure code is normalized (Sample${fileNameSuffix})`, async () => {
239-
configurationService
240-
.setup((c) => c.getSettings(TypeMoq.It.isAny()))
241-
.returns({
242-
REPL: {
243-
EnableREPLSmartSend: false,
244-
REPLSmartSend: false,
245-
},
246-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
247-
} as any);
248-
const code = await fs.readFile(path.join(TEST_FILES_PATH, `sample${fileNameSuffix}_raw.py`), 'utf8');
249-
const expectedCode = await fs.readFile(
250-
path.join(TEST_FILES_PATH, `sample${fileNameSuffix}_normalized_selection.py`),
251-
'utf8',
252-
);
253-
await ensureCodeIsNormalized(code, expectedCode);
237+
const pythonTestVersion = await getPythonSemVer();
238+
if (pythonTestVersion && pythonTestVersion.minor < 13) {
239+
['', '1', '2', '3', '4', '5', '6', '7', '8'].forEach((fileNameSuffix) => {
240+
test(`Ensure code is normalized (Sample${fileNameSuffix}) - Python < 3.13`, async () => {
241+
configurationService
242+
.setup((c) => c.getSettings(TypeMoq.It.isAny()))
243+
.returns({
244+
REPL: {
245+
EnableREPLSmartSend: false,
246+
REPLSmartSend: false,
247+
},
248+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
249+
} as any);
250+
const code = await fs.readFile(path.join(TEST_FILES_PATH, `sample${fileNameSuffix}_raw.py`), 'utf8');
251+
const expectedCode = await fs.readFile(
252+
path.join(TEST_FILES_PATH, `sample${fileNameSuffix}_normalized_selection.py`),
253+
'utf8',
254+
);
255+
await ensureCodeIsNormalized(code, expectedCode);
256+
});
254257
});
255-
});
258+
}
256259

257260
test("Display message if there's no active file", async () => {
258261
documentManager.setup((doc) => doc.activeTextEditor).returns(() => undefined);

0 commit comments

Comments
 (0)