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

Launch Native REPL using terminal link #24734

Merged
merged 22 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 17 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
5 changes: 5 additions & 0 deletions python_files/pythonrc.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,8 @@ def __str__(self):

if sys.platform != "win32" and (not is_wsl) and use_shell_integration:
sys.ps1 = PS1()

if sys.platform == "darwin":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it weird if only part of this string is fixed to create a link with l10n? Will that translate only half the command?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean tooltip: l10n.t('Launch VS Code Native REPL'), part?
I think the the tooltip would only show Launch VS Code Native REPL

print("Cmd click to launch VS Code Native REPL")
else:
print("Ctrl click to launch VS Code Native REPL")
20 changes: 20 additions & 0 deletions python_files/tests/test_shell_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,23 @@ def test_excepthook_call():

hooks.my_excepthook("mock_type", "mock_value", "mock_traceback")
mock_excepthook.assert_called_once_with("mock_type", "mock_value", "mock_traceback")


if sys.platform == "darwin":

def test_print_statement_darwin(monkeypatch):
importlib.reload(pythonrc)
with monkeypatch.context() as m:
m.setattr("builtins.print", Mock())
importlib.reload(sys.modules["pythonrc"])
print.assert_any_call("Cmd click to launch VS Code Native REPL")


if sys.platform == "win32":

def test_print_statement_non_darwin(monkeypatch):
importlib.reload(pythonrc)
with monkeypatch.context() as m:
m.setattr("builtins.print", Mock())
importlib.reload(sys.modules["pythonrc"])
print.assert_any_call("Ctrl click to launch VS Code Native REPL")
5 changes: 5 additions & 0 deletions src/client/common/vscodeApis/windowApis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
TerminalShellExecutionStartEvent,
LogOutputChannel,
OutputChannel,
TerminalLinkProvider,
} from 'vscode';
import { createDeferred, Deferred } from '../utils/async';
import { Resource } from '../types';
Expand Down Expand Up @@ -258,3 +259,7 @@ export function createOutputChannel(name: string, languageId?: string): OutputCh
export function createLogOutputChannel(name: string, options: { log: true }): LogOutputChannel {
return window.createOutputChannel(name, options);
}

export function registerTerminalLinkProvider(provider: TerminalLinkProvider): Disposable {
return window.registerTerminalLinkProvider(provider);
}
2 changes: 2 additions & 0 deletions src/client/extensionActivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { registerReplCommands, registerReplExecuteOnEnter, registerStartNativeRe
import { registerTriggerForTerminalREPL } from './terminals/codeExecution/terminalReplWatcher';
import { registerPythonStartup } from './terminals/pythonStartup';
import { registerPixiFeatures } from './pythonEnvironments/common/environmentManagers/pixi';
import { registerCustomTerminalLinkProvider } from './terminals/pythonStartupLinkProvider';

export async function activateComponents(
// `ext` is passed to any extra activation funcs.
Expand Down Expand Up @@ -115,6 +116,7 @@ export function activateFeatures(ext: ExtensionState, _components: Components):
registerStartNativeReplCommand(ext.disposables, interpreterService);
registerReplCommands(ext.disposables, interpreterService, executionHelper, commandManager);
registerReplExecuteOnEnter(ext.disposables, interpreterService, commandManager);
registerCustomTerminalLinkProvider(ext.disposables);
}

/// //////////////////////////
Expand Down
47 changes: 47 additions & 0 deletions src/client/terminals/pythonStartupLinkProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* eslint-disable class-methods-use-this */
import {
CancellationToken,
Disposable,
ProviderResult,
TerminalLink,
TerminalLinkContext,
TerminalLinkProvider,
l10n,
} from 'vscode';
import { executeCommand } from '../common/vscodeApis/commandApis';
import { registerTerminalLinkProvider } from '../common/vscodeApis/windowApis';

interface CustomTerminalLink extends TerminalLink {
command: string;
}

export class CustomTerminalLinkProvider implements TerminalLinkProvider<CustomTerminalLink> {
provideTerminalLinks(
context: TerminalLinkContext,
_token: CancellationToken,
): ProviderResult<CustomTerminalLink[]> {
const links: CustomTerminalLink[] = [];
// Question: What if context.line is truncated because of user zoom setting?
anthonykim1 marked this conversation as resolved.
Show resolved Hide resolved
// Meaning what if this line is separated into two+ line in terminal?
const expectedNativeLink = 'VS Code Native REPL';

// eslint-disable-next-line no-cond-assign
anthonykim1 marked this conversation as resolved.
Show resolved Hide resolved
if (context.line.includes(expectedNativeLink)) {
links.push({
startIndex: context.line.indexOf(expectedNativeLink),
length: expectedNativeLink.length,
tooltip: l10n.t('Launch VS Code Native REPL'),
command: 'python.startNativeREPL',
});
}
return links;
}

async handleTerminalLink(link: CustomTerminalLink): Promise<void> {
await executeCommand(link.command);
}
}

export function registerCustomTerminalLinkProvider(disposables: Disposable[]): void {
disposables.push(registerTerminalLinkProvider(new CustomTerminalLinkProvider()));
}
18 changes: 16 additions & 2 deletions src/test/terminals/shellIntegration/pythonStartup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

import * as sinon from 'sinon';
import * as TypeMoq from 'typemoq';
import { GlobalEnvironmentVariableCollection, Uri, WorkspaceConfiguration } from 'vscode';
import { GlobalEnvironmentVariableCollection, Uri, WorkspaceConfiguration, Disposable } from 'vscode';
import * as workspaceApis from '../../../client/common/vscodeApis/workspaceApis';
import { registerPythonStartup } from '../../../client/terminals/pythonStartup';
import { IExtensionContext } from '../../../client/common/types';
import * as pythonStartupLinkProvider from '../../../client/terminals/pythonStartupLinkProvider';

suite('Terminal - Shell Integration with PYTHONSTARTUP', () => {
let getConfigurationStub: sinon.SinonStub;
Expand All @@ -20,7 +21,6 @@ suite('Terminal - Shell Integration with PYTHONSTARTUP', () => {
setup(() => {
context = TypeMoq.Mock.ofType<IExtensionContext>();
globalEnvironmentVariableCollection = TypeMoq.Mock.ofType<GlobalEnvironmentVariableCollection>();

// Question: Why do we have to set up environmentVariableCollection and globalEnvironmentVariableCollection in this flip-flop way?
// Reference: /vscode-python/src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts
context.setup((c) => c.environmentVariableCollection).returns(() => globalEnvironmentVariableCollection.object);
Expand Down Expand Up @@ -122,4 +122,18 @@ suite('Terminal - Shell Integration with PYTHONSTARTUP', () => {

globalEnvironmentVariableCollection.verify((c) => c.delete('PYTHONSTARTUP'), TypeMoq.Times.once());
});

test('Ensure registering terminal link calls registerTerminalLinkProvider', async () => {
anthonykim1 marked this conversation as resolved.
Show resolved Hide resolved
const registerTerminalLinkProviderStub = sinon.stub(
pythonStartupLinkProvider,
'registerCustomTerminalLinkProvider',
);
const disposableArray: Disposable[] = [];
pythonStartupLinkProvider.registerCustomTerminalLinkProvider(disposableArray);

sinon.assert.calledOnce(registerTerminalLinkProviderStub);
sinon.assert.calledWith(registerTerminalLinkProviderStub, disposableArray);

registerTerminalLinkProviderStub.restore();
});
});
Loading