8000 PyScript Terminal - the latest kind by WebReflection · Pull Request #1816 · pyscript/pyscript · GitHub
[go: up one dir, main page]

Skip to content

PyScript Terminal - the latest kind #1816

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

Merged
merged 1 commit into from
Oct 31, 2023
Merged

PyScript Terminal - the latest kind #1816

merged 1 commit into from
Oct 31, 2023

Conversation

WebReflection
Copy link
Contributor
@WebReflection WebReflection commented Oct 27, 2023

Description

This MR brings in what agreed and discussed in #1801 (comment)

Changes

  • rewrite the whole thing after new hooks landed
  • improved the plugins ergonomics by awaiting these if they return a promise as default
  • test via smoke test everything is fine
  • the terminal is still one per page and it allows workers to have full acces while on main it's still read-only for users

Checklist

  • All tests pass locally
  • I have updated docs/changelog.md
  • I have created documentation for this(if applicable)

Copy link
Contributor
@fpliger fpliger left a comment

Choose a reason for hiding this comment

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

This is a great PR! Love it.

Only question that I have (and can come as a follow up PR), is to actually add an automated test

@WebReflection
Copy link
Contributor Author

@fpliger

Only question that I have (and can come as a follow up PR), is to actually add an automated test

I'd love one too but I've no idea how to do that ... the terminal takes logs (print) and/or errors and I am not sure how that test would look like ... the smoke test is OK to replicate as integration but then ... what do I check? Maybe a filter of the logs would be enough, maybe not?

@antocuni I also would like to know your opinion here as I am still using code to instrument Python but maybe, thanks to later hooks, I can do something else passing over a callback instead of python code? I think the codeAfterRun is still needed to open the terminal, but I am not super sure the codeBeforeRun is the best approach, as we can now directly instrument the pyodide interpreter ... thoughts, if you have any time? Tanks!

return _sync.pyterminal_read(prompt)

_py_terminal = _PyTerminal()
sys.stdout = sys.stderr = _py_terminal
Copy link
Contributor

Choose a reason for hiding this comment

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

This is going to be problematic because _PyTerminal doesn't implement the IOBase interface. I think adding isatty and flush methods would be good enough, since a lot of things ask about sys.stdout.isatty. Though for instance, pytest tries to do os.dup2(devnull, sys.stdout.fileno()) which will basically only work if you use pyodide.setStdout() (or make your own file system device). Is there a reason that setStdout isn't working for you?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

are you suggesting I should completely ditch this Python evaluation and just use pyodide to setup the terminal properly? I am OK with that, and no reasons not to do so ... which is why I've asked @antocuni what was the thing that was missing ... I still need to use the sync feature though, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

are you suggesting I should completely ditch this Python evaluation and just use pyodide to setup the terminal properly?

Yeah assuming I understand what you mean by that.

I still need to use the sync feature though, right?

I think so yes, but in a js function.

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, I think that if we can use pyodide.setStdio it would save us a lot of headaches.
This python code was a quick&dirty hack to have something kind-of-working in the worker, but then I was stuck by the impossibility to execute custom JS code in the worker (and thus I was unable to call pyodide.setdStdio).

Copy link
Contributor

Choose a reason for hiding this comment

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

You can do

from pyodide_js import setStdio

and then call setStdio from Python. But you'll need a JS or C function to be the handler (if you try to pass a Python function it'll throw a GilNotHeldError).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll try that but I meant that this example is nowhere found online or in the docs 😅 ... or at least I couldn't find it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hoodmane

You should do something like ...

that doesn't work. The read expects a prompt to read (same as input expects a prompt) + encodeInto excepts 2 arguments so all things don't quite work as presented ... I think a live demo that shows how setStdin can be used for text would be helpful? ... I am currently keen to keep things as they are and eventually refine later, but at least I can make stdout and stderr isatty

Copy link
Contributor

Choose a reason for hiding this comment

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

read expects a prompt to read

That's not correct, read should take no arguments. The way that prompt should work is as two separate steps:

  1. write the prompt text
  2. read to get user data

encodeInto expects 2 arguments

Oops it should be encoder.encodeInto(dataStr, buffer):

let dataStr;
const encoder = new TextEncoder();
const stdin = {
    read: buffer => {
        if (!dataStr) {
           dataStr = sync.pyterminal_read();
        }
        const {read, written} = encoder.encodeInto(dataStr, buffer);
        dataStr = dataStr.slice(read);
        return written;
    },
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is still the part that doesn't work:

dataStr = sync.pyterminal_read();

the read expects some value ... I might try passing an empty string but read there acts like input(prompt) and returns the text later ... that's how XTerm uses is too.

Thanks for the update though, I'll see if I can manage to make it work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I might have something working as expected after a bit of playing around the needed dance to have an equivalent DX #1833 if you could have a quick look at the workerReady callback maybe you can tell me if that makes sense or if it doesn't ... all I know is that it's the only one that worked to me. Thank you!

@WebReflection
Copy link
Contributor Author

OK, this is ready to go ... I am only waiting to understand if isatty: true is necessary for setStdout or setStderr but that could also be a follow up amend ... everything looks good to me out of the smoke test.

@WebReflection WebReflection merged commit 72f2665 into pyscript:main Oct 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0