Skip to content

Commit

Permalink
add bracketed paste mode for Python3.13 and above (#24401)
Browse files Browse the repository at this point in the history
Resolves: #23843
Related: python/cpython#126449
  • Loading branch information
anthonykim1 authored Nov 8, 2024
1 parent 785ed68 commit aae2b63
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 4 deletions.
14 changes: 11 additions & 3 deletions python_files/normalizeSelection.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import textwrap
from typing import Iterable

attach_bracket_paste = sys.version_info >= (3, 13)


def split_lines(source):
"""
Expand Down Expand Up @@ -279,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})
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"))
Expand Down
9 changes: 9 additions & 0 deletions src/client/terminals/codeExecution/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ 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) {
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) {
Expand Down
64 changes: 63 additions & 1 deletion src/test/terminals/codeExecution/helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -49,6 +50,7 @@ suite('Terminal - Code Execution Helper', () => {
let workspaceService: TypeMoq.IMock<IWorkspaceService>;
let configurationService: TypeMoq.IMock<IConfigurationService>;
let pythonSettings: TypeMoq.IMock<IPythonSettings>;
let jsonParseStub: sinon.SinonStub;
const workingPython: PythonEnvironment = {
path: PYTHON_PATH,
version: new SemVer('3.6.6-final'),
Expand Down Expand Up @@ -134,7 +136,68 @@ 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 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 = '';

processService
Expand Down Expand Up @@ -186,7 +249,6 @@ suite('Terminal - Code Execution Helper', () => {
path.join(TEST_FILES_PATH, `sample${fileNameSuffix}_normalized_selection.py`),
'utf8',
);

await ensureCodeIsNormalized(code, expectedCode);
});
});
Expand Down

0 comments on commit aae2b63

Please sign in to comment.