Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add bracketed paste mode for Python3.13 and above #24401

Merged
merged 5 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
10 changes: 10 additions & 0 deletions src/client/terminals/codeExecution/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
41 changes: 37 additions & 4 deletions 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 @@ -35,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<IActiveResourceService>;
let documentManager: TypeMoq.IMock<IDocumentManager>;
Expand All @@ -49,10 +50,11 @@ 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'),
sysVersion: '1.0.0.0',
version: new SemVer('3.13.0'),
sysVersion: '3.13.0',
sysPrefix: 'Python',
displayName: 'Python',
envType: EnvironmentType.Unknown,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
});
});
Expand Down
Loading