-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add Interactive REPL Experiment #23235
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
Changes from all commits
51b5f8e
51278d5
8dbbd05
5b76e4a
e92c539
bc15f8f
200ca1a
416ea96
71d3e22
a2208ca
c8a9b35
66515c6
2fede16
0f1488c
c3a3028
293a476
3b38072
e248afd
f329dd1
0bb23cf
19cee66
a4f7783
7258a5f
b6a8229
9f5deae
03062bd
bc9637c
fa76d00
6ff2dce
b8e8fb0
ed65b3a
1a378bb
a11c1c7
d9ba156
ea55d8b
da93090
c7f5372
289edb6
19e73b1
03d6ed3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
from typing import Dict, List, Optional, Union | ||
|
||
import sys | ||
import json | ||
import contextlib | ||
import io | ||
import traceback | ||
import uuid | ||
|
||
STDIN = sys.stdin | ||
STDOUT = sys.stdout | ||
STDERR = sys.stderr | ||
USER_GLOBALS = {} | ||
|
||
|
||
def send_message(msg: str): | ||
length_msg = len(msg) | ||
STDOUT.buffer.write(f"Content-Length: {length_msg}\r\n\r\n{msg}".encode(encoding="utf-8")) | ||
STDOUT.buffer.flush() | ||
|
||
|
||
def print_log(msg: str): | ||
send_message(json.dumps({"jsonrpc": "2.0", "method": "log", "params": msg})) | ||
|
||
|
||
def send_response(response: str, response_id: int): | ||
send_message(json.dumps({"jsonrpc": "2.0", "id": response_id, "result": response})) | ||
|
||
|
||
def send_request(params: Optional[Union[List, Dict]] = None): | ||
request_id = uuid.uuid4().hex | ||
if params is None: | ||
send_message(json.dumps({"jsonrpc": "2.0", "id": request_id, "method": "input"})) | ||
else: | ||
send_message( | ||
json.dumps({"jsonrpc": "2.0", "id": request_id, "method": "input", "params": params}) | ||
) | ||
return request_id | ||
|
||
|
||
original_input = input | ||
|
||
|
||
def custom_input(prompt=""): | ||
try: | ||
send_request({"prompt": prompt}) | ||
headers = get_headers() | ||
content_length = int(headers.get("Content-Length", 0)) | ||
|
||
if content_length: | ||
message_text = STDIN.read(content_length) | ||
message_json = json.loads(message_text) | ||
our_user_input = message_json["result"]["userInput"] | ||
return our_user_input | ||
except Exception: | ||
print_log(traceback.format_exc()) | ||
|
||
|
||
# Set input to our custom input | ||
USER_GLOBALS["input"] = custom_input | ||
input = custom_input | ||
|
||
|
||
def handle_response(request_id): | ||
while not STDIN.closed: | ||
try: | ||
headers = get_headers() | ||
content_length = int(headers.get("Content-Length", 0)) | ||
|
||
if content_length: | ||
message_text = STDIN.read(content_length) | ||
message_json = json.loads(message_text) | ||
our_user_input = message_json["result"]["userInput"] | ||
if message_json["id"] == request_id: | ||
send_response(our_user_input, message_json["id"]) | ||
elif message_json["method"] == "exit": | ||
sys.exit(0) | ||
|
||
except Exception: | ||
print_log(traceback.format_exc()) | ||
|
||
|
||
def exec_function(user_input): | ||
try: | ||
compile(user_input, "<stdin>", "eval") | ||
except SyntaxError: | ||
return exec | ||
return eval | ||
|
||
|
||
def execute(request, user_globals): | ||
str_output = CustomIO("<stdout>", encoding="utf-8") | ||
str_error = CustomIO("<stderr>", encoding="utf-8") | ||
|
||
with redirect_io("stdout", str_output): | ||
with redirect_io("stderr", str_error): | ||
str_input = CustomIO("<stdin>", encoding="utf-8", newline="\n") | ||
with redirect_io("stdin", str_input): | ||
exec_user_input(request["params"], user_globals) | ||
send_response(str_output.get_value(), request["id"]) | ||
|
||
|
||
def exec_user_input(user_input, user_globals): | ||
user_input = user_input[0] if isinstance(user_input, list) else user_input | ||
|
||
try: | ||
callable = exec_function(user_input) | ||
retval = callable(user_input, user_globals) | ||
if retval is not None: | ||
print(retval) | ||
except KeyboardInterrupt: | ||
print(traceback.format_exc()) | ||
except Exception: | ||
print(traceback.format_exc()) | ||
|
||
|
||
class CustomIO(io.TextIOWrapper): | ||
"""Custom stream object to replace stdio.""" | ||
|
||
def __init__(self, name, encoding="utf-8", newline=None): | ||
self._buffer = io.BytesIO() | ||
self._custom_name = name | ||
super().__init__(self._buffer, encoding=encoding, newline=newline) | ||
|
||
def close(self): | ||
"""Provide this close method which is used by some tools.""" | ||
# This is intentionally empty. | ||
|
||
def get_value(self) -> str: | ||
"""Returns value from the buffer as string.""" | ||
self.seek(0) | ||
return self.read() | ||
|
||
|
||
@contextlib.contextmanager | ||
def redirect_io(stream: str, new_stream): | ||
"""Redirect stdio streams to a custom stream.""" | ||
old_stream = getattr(sys, stream) | ||
setattr(sys, stream, new_stream) | ||
yield | ||
setattr(sys, stream, old_stream) | ||
|
||
|
||
def get_headers(): | ||
headers = {} | ||
while line := STDIN.readline().strip(): | ||
name, value = line.split(":", 1) | ||
headers[name] = value.strip() | ||
return headers | ||
|
||
|
||
if __name__ == "__main__": | ||
while not STDIN.closed: | ||
try: | ||
headers = get_headers() | ||
content_length = int(headers.get("Content-Length", 0)) | ||
|
||
if content_length: | ||
request_text = STDIN.read(content_length) | ||
request_json = json.loads(request_text) | ||
if request_json["method"] == "execute": | ||
execute(request_json, USER_GLOBALS) | ||
elif request_json["method"] == "exit": | ||
sys.exit(0) | ||
|
||
except Exception: | ||
print_log(traceback.format_exc()) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ | |
|
||
'use strict'; | ||
|
||
import { DebugConfigurationProvider, debug, languages, window } from 'vscode'; | ||
import { DebugConfigurationProvider, debug, languages, window, commands } from 'vscode'; | ||
|
||
import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; | ||
import { IExtensionActivationManager } from './activation/types'; | ||
|
@@ -16,6 +16,7 @@ import { IFileSystem } from './common/platform/types'; | |
import { | ||
IConfigurationService, | ||
IDisposableRegistry, | ||
IExperimentService, | ||
IExtensions, | ||
IInterpreterPathService, | ||
ILogOutputChannel, | ||
|
@@ -52,6 +53,8 @@ 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'; | ||
|
||
export async function activateComponents( | ||
// `ext` is passed to any extra activation funcs. | ||
|
@@ -105,6 +108,17 @@ export function activateFeatures(ext: ExtensionState, _components: Components): | |
interpreterService, | ||
pathUtils, | ||
); | ||
|
||
// Register native REPL context menu when in experiment | ||
const experimentService = ext.legacyIOC.serviceContainer.get<IExperimentService>(IExperimentService); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. legacy IOC? is there a newer one to use? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usually in other files in Python extension if we were to use serviceContainer, we access it via IServiceContainer, which we still could I believe if we register the REPL command in other file? I just thought extensionActivation was the best place to register command and read experiment service since it seemed like the fastest/prioritized when extension loads. |
||
commands.executeCommand('setContext', 'pythonRunREPL', false); | ||
if (experimentService) { | ||
const replExperimentValue = experimentService.inExperimentSync(EnableRunREPL.experiment); | ||
if (replExperimentValue) { | ||
registerReplCommands(ext.disposables, interpreterService); | ||
commands.executeCommand('setContext', 'pythonRunREPL', true); | ||
} | ||
} | ||
} | ||
|
||
/// ////////////////////////// | ||
|
Uh oh!
There was an error while loading. Please reload this page.