Skip to content

Commit aae2b63

Browse files
authored
add bracketed paste mode for Python3.13 and above (#24401)
Resolves: #23843 Related: python/cpython#126449
1 parent 785ed68 commit aae2b63

File tree

3 files changed

+83
-4
lines changed

3 files changed

+83
-4
lines changed

python_files/normalizeSelection.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import textwrap
99
from typing import Iterable
1010

11+
attach_bracket_paste = sys.version_info >= (3, 13)
12+
1113

1214
def split_lines(source):
1315
"""
@@ -279,14 +281,20 @@ def get_next_block_lineno(which_line_next):
279281
normalized = result["normalized_smart_result"]
280282
which_line_next = result["which_line_next"]
281283
if normalized == "deprecated":
282-
data = json.dumps({"normalized": normalized})
284+
data = json.dumps(
285+
{"normalized": normalized, "attach_bracket_paste": attach_bracket_paste}
286+
)
283287
else:
284288
data = json.dumps(
285-
{"normalized": normalized, "nextBlockLineno": result["which_line_next"]}
289+
{
290+
"normalized": normalized,
291+
"nextBlockLineno": result["which_line_next"],
292+
"attach_bracket_paste": attach_bracket_paste,
293+
}
286294
)
287295
else:
288296
normalized = normalize_lines(contents["code"])
289-
data = json.dumps({"normalized": normalized})
297+
data = json.dumps({"normalized": normalized, "attach_bracket_paste": attach_bracket_paste})
290298

291299
stdout = sys.stdout if sys.version_info < (3,) else sys.stdout.buffer
292300
stdout.write(data.encode("utf-8"))

src/client/terminals/codeExecution/helper.ts

+9
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ export class CodeExecutionHelper implements ICodeExecutionHelper {
118118
const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line - 1;
119119
await this.moveToNextBlock(lineOffset, activeEditor);
120120
}
121+
// For new _pyrepl for Python3.13 and above, we need to send code via bracketed paste mode.
122+
if (object.attach_bracket_paste) {
123+
let trimmedNormalized = object.normalized.replace(/\n$/, '');
124+
if (trimmedNormalized.endsWith(':\n')) {
125+
// In case where statement is unfinished via :, truncate so auto-indentation lands nicely.
126+
trimmedNormalized = trimmedNormalized.replace(/\n$/, '');
127+
}
128+
return `\u001b[200~${trimmedNormalized}\u001b[201~`;
129+
}
121130

122131
return parse(object.normalized);
123132
} catch (ex) {

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

+63-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as path from 'path';
88
import { SemVer } from 'semver';
99
import * as TypeMoq from 'typemoq';
1010
import { Position, Range, Selection, TextDocument, TextEditor, TextLine, Uri } from 'vscode';
11+
import * as sinon from 'sinon';
1112
import * as fs from '../../../client/common/platform/fs-paths';
1213
import {
1314
IActiveResourceService,
@@ -49,6 +50,7 @@ suite('Terminal - Code Execution Helper', () => {
4950
let workspaceService: TypeMoq.IMock<IWorkspaceService>;
5051
let configurationService: TypeMoq.IMock<IConfigurationService>;
5152
let pythonSettings: TypeMoq.IMock<IPythonSettings>;
53+
let jsonParseStub: sinon.SinonStub;
5254
const workingPython: PythonEnvironment = {
5355
path: PYTHON_PATH,
5456
version: new SemVer('3.6.6-final'),
@@ -134,7 +136,68 @@ suite('Terminal - Code Execution Helper', () => {
134136
editor.setup((e) => e.document).returns(() => document.object);
135137
});
136138

139+
test('normalizeLines should handle attach_bracket_paste correctly', async () => {
140+
configurationService
141+
.setup((c) => c.getSettings(TypeMoq.It.isAny()))
142+
.returns({
143+
REPL: {
144+
EnableREPLSmartSend: false,
145+
REPLSmartSend: false,
146+
},
147+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
148+
} as any);
149+
const actualProcessService = new ProcessService();
150+
processService
151+
.setup((p) => p.execObservable(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
152+
.returns((file, args, options) =>
153+
actualProcessService.execObservable.apply(actualProcessService, [file, args, options]),
154+
);
155+
156+
jsonParseStub = sinon.stub(JSON, 'parse');
157+
const mockResult = {
158+
normalized: 'print("Looks like you are on 3.13")',
159+
attach_bracket_paste: true,
160+
};
161+
jsonParseStub.returns(mockResult);
162+
163+
const result = await helper.normalizeLines('print("Looks like you are on 3.13")');
164+
165+
expect(result).to.equal(`\u001b[200~print("Looks like you are on 3.13")\u001b[201~`);
166+
jsonParseStub.restore();
167+
});
168+
169+
test('normalizeLines should not attach bracketed paste for < 3.13', async () => {
170+
jsonParseStub = sinon.stub(JSON, 'parse');
171+
const mockResult = {
172+
normalized: 'print("Looks like you are not on 3.13")',
173+
attach_bracket_paste: false,
174+
};
175+
jsonParseStub.returns(mockResult);
176+
177+
configurationService
178+
.setup((c) => c.getSettings(TypeMoq.It.isAny()))
179+
.returns({
180+
REPL: {
181+
EnableREPLSmartSend: false,
182+
REPLSmartSend: false,
183+
},
184+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
185+
} as any);
186+
const actualProcessService = new ProcessService();
187+
processService
188+
.setup((p) => p.execObservable(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
189+
.returns((file, args, options) =>
190+
actualProcessService.execObservable.apply(actualProcessService, [file, args, options]),
191+
);
192+
193+
const result = await helper.normalizeLines('print("Looks like you are not on 3.13")');
194+
195+
expect(result).to.equal('print("Looks like you are not on 3.13")');
196+
jsonParseStub.restore();
197+
});
198+
137199
test('normalizeLines should call normalizeSelection.py', async () => {
200+
jsonParseStub.restore();
138201
let execArgs = '';
139202

140203
processService
@@ -186,7 +249,6 @@ suite('Terminal - Code Execution Helper', () => {
186249
path.join(TEST_FILES_PATH, `sample${fileNameSuffix}_normalized_selection.py`),
187250
'utf8',
188251
);
189-
190252
await ensureCodeIsNormalized(code, expectedCode);
191253
});
192254
});

0 commit comments

Comments
 (0)