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

Allow execute on enter and Intellisense for native REPL with notebook UI #23442

Merged
merged 33 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
5f279f1
start adding command
anthonykim1 May 16, 2024
7992d4b
allow execute on enter and execute on blank space with hard coded bool
anthonykim1 May 16, 2024
31f7db4
remove unintended enum to experiment
anthonykim1 May 17, 2024
4523502
change to TODO
anthonykim1 May 17, 2024
6defed9
compile() arg 1 must be a string, bytes or AST object
anthonykim1 May 17, 2024
ac7cc8c
WORKING --PROPERLY CHECKING IF SOMETHING IS VALID AND COMPLETE
anthonykim1 May 17, 2024
6389fdb
Try to handle interrupt on windows
anthonykim1 May 20, 2024
e3b974c
hide execute on enter from command palette
anthonykim1 May 20, 2024
3039670
add execInREPLEnter to package.nls.json
anthonykim1 May 20, 2024
e6034b7
temp save interactive.open and multi-line
anthonykim1 May 21, 2024
ab490dd
temp save --> sending to IW, intellisense pt2
anthonykim1 May 21, 2024
e25ec45
refactoring, adding disposables
anthonykim1 May 21, 2024
58b7772
cleanup
anthonykim1 May 21, 2024
931f3bf
remove handling Windows interrupt for now
anthonykim1 May 21, 2024
f1dd8a0
more cleanup
anthonykim1 May 21, 2024
55cad61
rename variable
anthonykim1 May 21, 2024
0593349
shift enter sends to REPL or Terminal depending on setting
anthonykim1 May 22, 2024
dbf48e6
hide context menu depending on setting
anthonykim1 May 22, 2024
cfed9d7
fixing test
anthonykim1 May 22, 2024
76d6164
fix more test
anthonykim1 May 22, 2024
c0bc3b8
remove execInREPLEnter from package.json and nls.json
anthonykim1 May 22, 2024
f512670
Update package.nls.json
anthonykim1 May 23, 2024
1b2c451
match setting python.sendToNativeREPL.description
anthonykim1 May 23, 2024
d369f11
Update package.nls.json
anthonykim1 May 23, 2024
8b27c2f
Update src/client/repl/pythonServer.ts
anthonykim1 May 23, 2024
a745cb1
switch checkValidCommand to return boolean
anthonykim1 May 23, 2024
393d2fd
Update src/client/repl/replCommands.ts
anthonykim1 May 23, 2024
0cffa5a
Stop using var!
anthonykim1 May 23, 2024
a6036cd
delete stale experiment, properly get setting
anthonykim1 May 23, 2024
57b2bdd
refactor registerReplCommands
anthonykim1 May 23, 2024
0788e99
clean up more
anthonykim1 May 23, 2024
910783b
allow users with shift+enter functional
anthonykim1 May 23, 2024
e270705
properly add to disposables
anthonykim1 May 23, 2024
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
42 changes: 31 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -442,8 +442,7 @@
"pythonDiscoveryUsingWorkers",
"pythonTestAdapter",
"pythonREPLSmartSend",
"pythonRecommendTensorboardExt",
"pythonRunREPL"
"pythonRecommendTensorboardExt"
],
"enumDescriptions": [
"%python.experiments.All.description%",
Expand All @@ -453,8 +452,7 @@
"%python.experiments.pythonDiscoveryUsingWorkers.description%",
"%python.experiments.pythonTestAdapter.description%",
"%python.experiments.pythonREPLSmartSend.description%",
"%python.experiments.pythonRecommendTensorboardExt.description%",
"%python.experiments.pythonRunREPL.description%"
"%python.experiments.pythonRecommendTensorboardExt.description%"
]
},
"scope": "window",
Expand All @@ -472,8 +470,7 @@
"pythonTerminalEnvVarActivation",
"pythonDiscoveryUsingWorkers",
"pythonTestAdapter",
"pythonREPLSmartSend",
"pythonRunREPL"
"pythonREPLSmartSend"
],
"enumDescriptions": [
"%python.experiments.All.description%",
Expand All @@ -482,8 +479,7 @@
"%python.experiments.pythonTerminalEnvVarActivation.description%",
"%python.experiments.pythonDiscoveryUsingWorkers.description%",
"%python.experiments.pythonTestAdapter.description%",
"%python.experiments.pythonREPLSmartSend.description%",
"%python.experiments.pythonRunREPL.description%"
"%python.experiments.pythonREPLSmartSend.description%"
]
},
"scope": "window",
Expand Down Expand Up @@ -628,6 +624,15 @@
"scope": "resource",
"type": "boolean"
},
"python.REPL.sendToNativeREPL": {
Copy link
Member

Choose a reason for hiding this comment

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

This does not have the experimental tag?

Copy link
Author

@anthonykim1 anthonykim1 May 23, 2024

Choose a reason for hiding this comment

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

Right, this is the settings which the value is ultimately overridden by the experiment value "pythonRunREPL".
Initially thought everything was just going to be experiment, but got convoluted with going the settings based experiment route

Following:

We also should get rid of the experiment item that we added for this, as the setting itself can be controlled via experiment by using the settings tag.

I think I would need to replace of existing "pythonRunREPL" from just experiment to settings (basically replace the setting tag from sendToNativeREPL to pythonRunREPL ). Would that allow us to leave the control tower as is? @cwebster-99

Copy link
Member

@karthiknadig karthiknadig May 23, 2024

Choose a reason for hiding this comment

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

I think it would be the fully require name python.REPL.sendToNativeREPL.

"default": false,
"description": "%python.REPL.sendToNativeREPL.description%",
"scope": "resource",
"type": "boolean",
"tags": [
"experimental"
]
},
"python.testing.autoTestDiscoverOnSaveEnabled": {
"default": true,
"description": "%python.testing.autoTestDiscoverOnSaveEnabled.description%",
Expand Down Expand Up @@ -1108,7 +1113,22 @@
{
"command": "python.execSelectionInTerminal",
"key": "shift+enter",
"when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused && activeEditor != 'workbench.editor.interactive'"
"when": "!config.python.REPL.sendToNativeREPL && editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused && activeEditor != 'workbench.editor.interactive'"
},
{
"command": "python.execInREPL",
"key": "shift+enter",
"when": "config.python.REPL.sendToNativeREPL && activeEditor != 'workbench.editor.interactive'"
},
{
"command": "python.execREPLShiftEnter",
"key": "shift+enter",
"when": "activeEditor == 'workbench.editor.interactive' && config.interactiveWindow.executeWithShiftEnter"
},
{
"command": "python.execInREPLEnter",
"key": "enter",
"when": "!config.interactiveWindow.executeWithShiftEnter && activeEditor == 'workbench.editor.interactive'"
},
{
"command": "python.refreshTensorBoard",
Expand Down Expand Up @@ -1367,12 +1387,12 @@
{
"command": "python.execSelectionInTerminal",
"group": "Python",
"when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported"
"when": "!config.python.REPL.sendToNativeREPL && editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported"
},
{
"command": "python.execInREPL",
"group": "Python",
"when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported && pythonRunREPL"
"when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported && config.python.REPL.sendToNativeREPL"
}
],
"editor/title": [
Expand Down
2 changes: 1 addition & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"python.experiments.pythonTestAdapter.description": "Denotes the Python Test Adapter experiment.",
"python.experiments.pythonREPLSmartSend.description": "Denotes the Python REPL Smart Send experiment.",
"python.experiments.pythonRecommendTensorboardExt.description": "Denotes the Tensorboard Extension recommendation experiment.",
"python.experiments.pythonRunREPL.description": "Enables users to run code in interactive Python REPL.",
"python.globalModuleInstallation.description": "Whether to install Python modules globally when not using an environment.",
"python.languageServer.description": "Defines type of the language server.",
"python.languageServer.defaultDescription": "Automatically select a language server: Pylance if installed and available, otherwise fallback to Jedi.",
Expand All @@ -63,6 +62,7 @@
"python.pipenvPath.description": "Path to the pipenv executable to use for activation.",
"python.poetryPath.description": "Path to the poetry executable.",
"python.EnableREPLSmartSend.description": "Toggle Smart Send for the Python REPL. Smart Send enables sending the smallest runnable block of code to the REPL on Shift+Enter and moves the cursor accordingly.",
"python.REPL.sendToNativeREPL.description": "Toggle to send code to Python REPL instead of the terminal on execution. Turning this on will change the behavior for both Smart Send and Run Selection/Line in the Context Menu.",
"python.tensorBoard.logDirectory.description": "Set this setting to your preferred TensorBoard log directory to skip log directory prompt when starting TensorBoard.",
"python.tensorBoard.logDirectory.markdownDeprecationMessage": "Tensorboard support has been moved to the extension [Tensorboard extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.tensorboard). Instead use the setting `tensorBoard.logDirectory`.",
"python.tensorBoard.logDirectory.deprecationMessage": "Tensorboard support has been moved to the extension Tensorboard extension. Instead use the setting `tensorBoard.logDirectory`.",
Expand Down
13 changes: 12 additions & 1 deletion python_files/python_server.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from typing import Dict, List, Optional, Union

import sys
import json
import contextlib
import io
import traceback
import uuid
import ast

STDIN = sys.stdin
STDOUT = sys.stdout
Expand Down Expand Up @@ -88,6 +88,15 @@ def exec_function(user_input):
return eval


def check_valid_command(request):
try:
user_input = request["params"]
ast.parse(user_input[0])
send_response("True", request["id"])
except SyntaxError:
send_response("False", request["id"])


def execute(request, user_globals):
str_output = CustomIO("<stdout>", encoding="utf-8")
str_error = CustomIO("<stderr>", encoding="utf-8")
Expand Down Expand Up @@ -160,6 +169,8 @@ def get_headers():
request_json = json.loads(request_text)
if request_json["method"] == "execute":
execute(request_json, USER_GLOBALS)
if request_json["method"] == "check_valid_command":
check_valid_command(request_json)
elif request_json["method"] == "exit":
sys.exit(0)

Expand Down
1 change: 1 addition & 0 deletions src/client/common/application/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ interface ICommandNameWithoutArgumentTypeMapping {
[Commands.Exec_Selection_In_Terminal]: [];
[Commands.Exec_Selection_In_Django_Shell]: [];
[Commands.Exec_In_REPL]: [];
[Commands.Exec_In_REPL_Enter]: [];
[Commands.Create_Terminal]: [];
[Commands.PickLocalProcess]: [];
[Commands.ClearStorage]: [];
Expand Down
2 changes: 2 additions & 0 deletions src/client/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export namespace Commands {
export const Exec_In_Separate_Terminal = 'python.execInDedicatedTerminal';
export const Exec_In_REPL = 'python.execInREPL';
export const Exec_Selection_In_Django_Shell = 'python.execSelectionInDjangoShell';
export const Exec_In_REPL_Enter = 'python.execInREPLEnter';
export const Exec_In_REPL_Shift_Enter = 'python.execREPLShiftEnter';
export const Exec_Selection_In_Terminal = 'python.execSelectionInTerminal';
export const GetSelectedInterpreterPath = 'python.interpreterPath';
export const InstallJupyter = 'python.installJupyter';
Expand Down
5 changes: 0 additions & 5 deletions src/client/common/experiments/groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,3 @@ export enum RecommendTensobardExtension {
export enum CreateEnvOnPipInstallTrigger {
experiment = 'pythonCreateEnvOnPipInstall',
}

// Experiment to enable running Python REPL using IW.
export enum EnableRunREPL {
experiment = 'pythonRunREPL',
}
2 changes: 1 addition & 1 deletion src/client/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export interface ITerminalSettings {

export interface IREPLSettings {
readonly enableREPLSmartSend: boolean;
readonly enableIWREPL: boolean;
readonly sendToNativeREPL: boolean;
}

export interface IExperiments {
Expand Down
11 changes: 11 additions & 0 deletions src/client/common/vscodeApis/windowApis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
TerminalShellExecutionStartEvent,
} from 'vscode';
import { createDeferred, Deferred } from '../utils/async';
import { Resource } from '../types';
import { getWorkspaceFolders } from './workspaceApis';

export function showTextDocument(uri: Uri): Thenable<TextEditor> {
return window.showTextDocument(uri);
Expand Down Expand Up @@ -238,3 +240,12 @@ export function createStepForwardEndNode<T>(deferred?: Deferred<T>, result?: T):
undefined,
);
}

export function getActiveResource(): Resource {
const editor = window.activeTextEditor;
if (editor && !editor.document.isUntitled) {
return editor.document.uri;
}
const workspaces = getWorkspaceFolders();
return Array.isArray(workspaces) && workspaces.length > 0 ? workspaces[0].uri : undefined;
}
19 changes: 5 additions & 14 deletions src/client/extensionActivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

'use strict';

import { DebugConfigurationProvider, debug, languages, window, commands } from 'vscode';
import { DebugConfigurationProvider, debug, languages, window } from 'vscode';

import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry';
import { IExtensionActivationManager } from './activation/types';
Expand All @@ -16,7 +16,6 @@ import { IFileSystem } from './common/platform/types';
import {
IConfigurationService,
IDisposableRegistry,
IExperimentService,
IExtensions,
IInterpreterPathService,
ILogOutputChannel,
Expand Down Expand Up @@ -53,8 +52,7 @@ import { initializePersistentStateForTriggers } from './common/persistentState';
import { logAndNotifyOnLegacySettings } from './logging/settingLogs';
import { DebuggerTypeName } from './debugger/constants';
import { StopWatch } from './common/utils/stopWatch';
import { registerReplCommands } from './repl/replCommands';
import { EnableRunREPL } from './common/experiments/groups';
import { registerReplCommands, registerReplExecuteOnEnter, registerReplExecuteOnShiftEnter } from './repl/replCommands';

export async function activateComponents(
// `ext` is passed to any extra activation funcs.
Expand Down Expand Up @@ -109,16 +107,9 @@ export function activateFeatures(ext: ExtensionState, _components: Components):
pathUtils,
);

// Register native REPL context menu when in experiment
const experimentService = ext.legacyIOC.serviceContainer.get<IExperimentService>(IExperimentService);
commands.executeCommand('setContext', 'pythonRunREPL', false);
if (experimentService) {
const replExperimentValue = experimentService.inExperimentSync(EnableRunREPL.experiment);
if (replExperimentValue) {
registerReplCommands(ext.disposables, interpreterService);
commands.executeCommand('setContext', 'pythonRunREPL', true);
}
}
registerReplCommands(ext.disposables, interpreterService);
registerReplExecuteOnEnter(ext.disposables, interpreterService);
registerReplExecuteOnShiftEnter(ext.disposables);
}

/// //////////////////////////
Expand Down
33 changes: 26 additions & 7 deletions src/client/repl/pythonServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,32 @@ import { EXTENSION_ROOT_DIR } from '../constants';
import { traceError, traceLog } from '../logging';

const SERVER_PATH = path.join(EXTENSION_ROOT_DIR, 'python_files', 'python_server.py');
let serverInstance: PythonServer | undefined;

export interface PythonServer extends Disposable {
execute(code: string): Promise<string>;
interrupt(): void;
input(): void;
checkValidCommand(code: string): Promise<boolean>;
}

class PythonServerImpl implements Disposable {
private readonly disposables: Disposable[] = [];

constructor(private connection: rpc.MessageConnection, private pythonServer: ch.ChildProcess) {
this.initialize();
this.input();
}

private initialize(): void {
this.connection.onNotification('log', (message: string) => {
console.log('Log:', message);
});
this.disposables.push(
this.connection.onNotification('log', (message: string) => {
console.log('Log:', message);
}),
);
this.connection.listen();
}

// Register input handler
public input(): void {
// Register input request handler
this.connection.onRequest('input', async (request) => {
Expand All @@ -49,18 +54,32 @@ class PythonServerImpl implements Disposable {
}

public interrupt(): void {
// Passing SIGINT to interrupt only would work for Mac and Linux
if (this.pythonServer.kill('SIGINT')) {
traceLog('Python server interrupted');
traceLog('Python REPL server interrupted');
}
}

public async checkValidCommand(code: string): Promise<boolean> {
const completeCode = await this.connection.sendRequest('check_valid_command', code);
if (completeCode === 'True') {
return new Promise((resolve) => resolve(true));
}
return new Promise((resolve) => resolve(false));
}

public dispose(): void {
this.connection.sendNotification('exit');
this.disposables.forEach((d) => d.dispose());
this.connection.dispose();
}
}

export function createPythonServer(interpreter: string[]): PythonServer {
if (serverInstance) {
return serverInstance;
}

const pythonServer = ch.spawn(interpreter[0], [...interpreter.slice(1), SERVER_PATH]);

pythonServer.stderr.on('data', (data) => {
Expand All @@ -76,6 +95,6 @@ export function createPythonServer(interpreter: string[]): PythonServer {
new rpc.StreamMessageReader(pythonServer.stdout),
new rpc.StreamMessageWriter(pythonServer.stdin),
);

return new PythonServerImpl(connection, pythonServer);
serverInstance = new PythonServerImpl(connection, pythonServer);
return serverInstance;
}
Loading
Loading