From eb51c96e523d7d2a8ff9f9d63cd999db86b68829 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 6 Nov 2024 12:33:35 -0800 Subject: [PATCH 1/5] add bracketed paste mode for Python3.13 and above --- python_files/normalizeSelection.py | 7 ++++--- src/client/terminals/codeExecution/helper.ts | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/python_files/normalizeSelection.py b/python_files/normalizeSelection.py index 981251289e57..598ebc654183 100644 --- a/python_files/normalizeSelection.py +++ b/python_files/normalizeSelection.py @@ -8,6 +8,7 @@ import textwrap from typing import Iterable +attach_bracket_paste = sys.version_info >= (3, 13) def split_lines(source): """ @@ -279,14 +280,14 @@ def get_next_block_lineno(which_line_next): normalized = result["normalized_smart_result"] which_line_next = result["which_line_next"] if normalized == "deprecated": - data = json.dumps({"normalized": normalized}) + data = json.dumps({"normalized": normalized, "attach_bracket_paste": attach_bracket_paste}) else: data = json.dumps( - {"normalized": normalized, "nextBlockLineno": result["which_line_next"]} + {"normalized": normalized, "nextBlockLineno": result["which_line_next"], "attach_bracket_paste": attach_bracket_paste} ) else: normalized = normalize_lines(contents["code"]) - data = json.dumps({"normalized": normalized}) + data = json.dumps({"normalized": normalized, "attach_bracket_paste": attach_bracket_paste }) stdout = sys.stdout if sys.version_info < (3,) else sys.stdout.buffer stdout.write(data.encode("utf-8")) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index ff1c4f218f8d..9c1ca3ab1598 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -118,6 +118,16 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line - 1; await this.moveToNextBlock(lineOffset, activeEditor); } + // For new _pyrepl for Python3.13 and above, we need to send code via bracketed paste mode. + if (object.attach_bracket_paste) { + // return `\u001b[200~${object.normalized.trim()}\u001b[201~`; + let trimmedNormalized = object.normalized.replace(/\n$/, ''); + if (trimmedNormalized.endsWith(':\n')) { + // In case where statement is unfinished via :, truncate so auto-indentation lands nicely. + trimmedNormalized = trimmedNormalized.replace(/\n$/, ''); + } + return `\u001b[200~${trimmedNormalized}\u001b[201~`; + } return parse(object.normalized); } catch (ex) { From ccebd33f249313b66c44dddac44880ca00d3d9f5 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 6 Nov 2024 12:47:54 -0800 Subject: [PATCH 2/5] add TODO --- src/test/terminals/codeExecution/helper.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index ebadd153705e..64694ee2c6d9 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -35,7 +35,7 @@ import { ICodeExecutionHelper } from '../../../client/terminals/types'; import { PYTHON_PATH } from '../../common'; const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'python_files', 'terminalExec'); - +// TODO: tests for 3.13 relevant sequences suite('Terminal - Code Execution Helper', () => { let activeResourceService: TypeMoq.IMock; let documentManager: TypeMoq.IMock; From 25b2f1702d164e8c563cfb72946152f5470f0c79 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 6 Nov 2024 12:51:09 -0800 Subject: [PATCH 3/5] ruff --- python_files/normalizeSelection.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/python_files/normalizeSelection.py b/python_files/normalizeSelection.py index 598ebc654183..3d5137fe4aeb 100644 --- a/python_files/normalizeSelection.py +++ b/python_files/normalizeSelection.py @@ -10,6 +10,7 @@ attach_bracket_paste = sys.version_info >= (3, 13) + def split_lines(source): """ Split selection lines in a version-agnostic way. @@ -280,14 +281,20 @@ def get_next_block_lineno(which_line_next): normalized = result["normalized_smart_result"] which_line_next = result["which_line_next"] if normalized == "deprecated": - data = json.dumps({"normalized": normalized, "attach_bracket_paste": attach_bracket_paste}) + data = json.dumps( + {"normalized": normalized, "attach_bracket_paste": attach_bracket_paste} + ) else: data = json.dumps( - {"normalized": normalized, "nextBlockLineno": result["which_line_next"], "attach_bracket_paste": attach_bracket_paste} + { + "normalized": normalized, + "nextBlockLineno": result["which_line_next"], + "attach_bracket_paste": attach_bracket_paste, + } ) else: normalized = normalize_lines(contents["code"]) - data = json.dumps({"normalized": normalized, "attach_bracket_paste": attach_bracket_paste }) + data = json.dumps({"normalized": normalized, "attach_bracket_paste": attach_bracket_paste}) stdout = sys.stdout if sys.version_info < (3,) else sys.stdout.buffer stdout.write(data.encode("utf-8")) From 73952bbe2479fd97cb619531f09d11a8eca253b1 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 6 Nov 2024 20:05:04 -0800 Subject: [PATCH 4/5] added and got test WORKING --- .../terminals/codeExecution/helper.test.ts | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 64694ee2c6d9..8a45ed77a922 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -8,6 +8,7 @@ import * as path from 'path'; import { SemVer } from 'semver'; import * as TypeMoq from 'typemoq'; import { Position, Range, Selection, TextDocument, TextEditor, TextLine, Uri } from 'vscode'; +import * as sinon from 'sinon'; import * as fs from '../../../client/common/platform/fs-paths'; import { IActiveResourceService, @@ -49,10 +50,11 @@ suite('Terminal - Code Execution Helper', () => { let workspaceService: TypeMoq.IMock; let configurationService: TypeMoq.IMock; let pythonSettings: TypeMoq.IMock; + let jsonParseStub: sinon.SinonStub; const workingPython: PythonEnvironment = { path: PYTHON_PATH, - version: new SemVer('3.6.6-final'), - sysVersion: '1.0.0.0', + version: new SemVer('3.13.0'), + sysVersion: '3.13.0', sysPrefix: 'Python', displayName: 'Python', envType: EnvironmentType.Unknown, @@ -134,7 +136,38 @@ suite('Terminal - Code Execution Helper', () => { editor.setup((e) => e.document).returns(() => document.object); }); + test('normalizeLines should handle attach_bracket_paste correctly', async () => { + configurationService + .setup((c) => c.getSettings(TypeMoq.It.isAny())) + .returns({ + REPL: { + EnableREPLSmartSend: false, + REPLSmartSend: false, + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); + const actualProcessService = new ProcessService(); + processService + .setup((p) => p.execObservable(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns((file, args, options) => + actualProcessService.execObservable.apply(actualProcessService, [file, args, options]), + ); + + jsonParseStub = sinon.stub(JSON, 'parse'); + const mockResult = { + normalized: 'print("Looks like you are on 3.13")', + attach_bracket_paste: true, + }; + jsonParseStub.returns(mockResult); + + const result = await helper.normalizeLines('print("Looks like you are on 3.13")'); + + expect(result).to.equal(`\u001b[200~print("Looks like you are on 3.13")\u001b[201~`); + jsonParseStub.restore(); + }); + test('normalizeLines should call normalizeSelection.py', async () => { + jsonParseStub.restore(); let execArgs = ''; processService @@ -186,7 +219,7 @@ suite('Terminal - Code Execution Helper', () => { path.join(TEST_FILES_PATH, `sample${fileNameSuffix}_normalized_selection.py`), 'utf8', ); - + // python3 -m pythonFiles.tests await ensureCodeIsNormalized(code, expectedCode); }); }); From 2cc70079dc7206e98fedbd1a790a702d0f863703 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Fri, 8 Nov 2024 09:39:47 -0800 Subject: [PATCH 5/5] add more test and clean up --- src/client/terminals/codeExecution/helper.ts | 1 - .../terminals/codeExecution/helper.test.ts | 37 +++++++++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 9c1ca3ab1598..49fdd59a00c0 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -120,7 +120,6 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { } // For new _pyrepl for Python3.13 and above, we need to send code via bracketed paste mode. if (object.attach_bracket_paste) { - // return `\u001b[200~${object.normalized.trim()}\u001b[201~`; let trimmedNormalized = object.normalized.replace(/\n$/, ''); if (trimmedNormalized.endsWith(':\n')) { // In case where statement is unfinished via :, truncate so auto-indentation lands nicely. diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 8a45ed77a922..7a3171ccf836 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -36,7 +36,7 @@ import { ICodeExecutionHelper } from '../../../client/terminals/types'; import { PYTHON_PATH } from '../../common'; const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'python_files', 'terminalExec'); -// TODO: tests for 3.13 relevant sequences + suite('Terminal - Code Execution Helper', () => { let activeResourceService: TypeMoq.IMock; let documentManager: TypeMoq.IMock; @@ -53,8 +53,8 @@ suite('Terminal - Code Execution Helper', () => { let jsonParseStub: sinon.SinonStub; const workingPython: PythonEnvironment = { path: PYTHON_PATH, - version: new SemVer('3.13.0'), - sysVersion: '3.13.0', + version: new SemVer('3.6.6-final'), + sysVersion: '1.0.0.0', sysPrefix: 'Python', displayName: 'Python', envType: EnvironmentType.Unknown, @@ -166,6 +166,36 @@ suite('Terminal - Code Execution Helper', () => { jsonParseStub.restore(); }); + test('normalizeLines should not attach bracketed paste for < 3.13', async () => { + jsonParseStub = sinon.stub(JSON, 'parse'); + const mockResult = { + normalized: 'print("Looks like you are not on 3.13")', + attach_bracket_paste: false, + }; + jsonParseStub.returns(mockResult); + + configurationService + .setup((c) => c.getSettings(TypeMoq.It.isAny())) + .returns({ + REPL: { + EnableREPLSmartSend: false, + REPLSmartSend: false, + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); + const actualProcessService = new ProcessService(); + processService + .setup((p) => p.execObservable(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns((file, args, options) => + actualProcessService.execObservable.apply(actualProcessService, [file, args, options]), + ); + + const result = await helper.normalizeLines('print("Looks like you are not on 3.13")'); + + expect(result).to.equal('print("Looks like you are not on 3.13")'); + jsonParseStub.restore(); + }); + test('normalizeLines should call normalizeSelection.py', async () => { jsonParseStub.restore(); let execArgs = ''; @@ -219,7 +249,6 @@ suite('Terminal - Code Execution Helper', () => { path.join(TEST_FILES_PATH, `sample${fileNameSuffix}_normalized_selection.py`), 'utf8', ); - // python3 -m pythonFiles.tests await ensureCodeIsNormalized(code, expectedCode); }); });