mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-22 19:53:00 -05:00
[PROPOSAL] REPL output replacement (#439)
* fix OutputManager _append setter * fix OutputManager change parameters * fix OutputCtxManager __init__ and change methods * replacing OutputManager pyscript.write with write function * add optional output-append attribute to py-repl * add appendOutput(default: true) to base component * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update pyscriptjs/src/components/pyrepl.ts Co-authored-by: woxtu <woxtup@gmail.com> * change from output-append flag to output-mode attribute * removed type annotation * repositioned setOutputMode call for auto-generated REPLs to work * fixed indentation error for indented input * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added preEvaluate method * moved output-mode logic to preEvaluate * remove static write method from PyScript, add write method to Element * removed err parameter from OutputCtxManager * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add PyScript.write back with a deprecation warning * fix wrong input name Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: woxtu <woxtup@gmail.com> Co-authored-by: Fabio Pliger <fabio.pliger@gmail.com>
This commit is contained in:
@@ -32,6 +32,7 @@ export class BaseEvalElement extends HTMLElement {
|
|||||||
outputElement: HTMLElement;
|
outputElement: HTMLElement;
|
||||||
errorElement: HTMLElement;
|
errorElement: HTMLElement;
|
||||||
theme: string;
|
theme: string;
|
||||||
|
appendOutput: boolean;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -40,6 +41,7 @@ export class BaseEvalElement extends HTMLElement {
|
|||||||
this.shadow = this.attachShadow({ mode: 'open' });
|
this.shadow = this.attachShadow({ mode: 'open' });
|
||||||
this.wrapper = document.createElement('slot');
|
this.wrapper = document.createElement('slot');
|
||||||
this.shadow.appendChild(this.wrapper);
|
this.shadow.appendChild(this.wrapper);
|
||||||
|
this.setOutputMode("append");
|
||||||
}
|
}
|
||||||
|
|
||||||
addToOutput(s: string) {
|
addToOutput(s: string) {
|
||||||
@@ -47,9 +49,30 @@ export class BaseEvalElement extends HTMLElement {
|
|||||||
this.outputElement.hidden = false;
|
this.outputElement.hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setOutputMode(defaultMode = "append") {
|
||||||
|
const mode = this.hasAttribute('output-mode') ? this.getAttribute('output-mode') : defaultMode;
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case "append":
|
||||||
|
this.appendOutput = true;
|
||||||
|
break;
|
||||||
|
case "replace":
|
||||||
|
this.appendOutput = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(`${this.id}: custom output-modes are currently not implemented`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// subclasses should overwrite this method to define custom logic
|
||||||
|
// before code gets evaluated
|
||||||
|
preEvaluate(): void {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// subclasses should overwrite this method to define custom logic
|
// subclasses should overwrite this method to define custom logic
|
||||||
// after code has been evaluated
|
// after code has been evaluated
|
||||||
postEvaluate() {
|
postEvaluate(): void {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +122,8 @@ export class BaseEvalElement extends HTMLElement {
|
|||||||
|
|
||||||
async evaluate(): Promise<void> {
|
async evaluate(): Promise<void> {
|
||||||
console.log('evaluate');
|
console.log('evaluate');
|
||||||
|
this.preEvaluate();
|
||||||
|
|
||||||
const pyodide = runtime;
|
const pyodide = runtime;
|
||||||
let source: string;
|
let source: string;
|
||||||
let output;
|
let output;
|
||||||
@@ -110,13 +135,13 @@ export class BaseEvalElement extends HTMLElement {
|
|||||||
|
|
||||||
if (source.includes('asyncio')) {
|
if (source.includes('asyncio')) {
|
||||||
await pyodide.runPythonAsync(
|
await pyodide.runPythonAsync(
|
||||||
`output_manager.change("` + this.outputElement.id + `", "` + this.errorElement.id + `")`,
|
`output_manager.change(out="${this.outputElement.id}", err="${this.errorElement.id}", append=${this.appendOutput ? 'True' : 'False'})`,
|
||||||
);
|
);
|
||||||
output = await pyodide.runPythonAsync(source);
|
output = await pyodide.runPythonAsync(source);
|
||||||
await pyodide.runPythonAsync(`output_manager.revert()`);
|
await pyodide.runPythonAsync(`output_manager.revert()`);
|
||||||
} else {
|
} else {
|
||||||
output = pyodide.runPython(
|
output = pyodide.runPython(
|
||||||
`output_manager.change("` + this.outputElement.id + `", "` + this.errorElement.id + `")`,
|
`output_manager.change(out="${this.outputElement.id}", err="${this.errorElement.id}", append=${this.appendOutput ? 'True' : 'False'})`,
|
||||||
);
|
);
|
||||||
output = pyodide.runPython(source);
|
output = pyodide.runPython(source);
|
||||||
pyodide.runPython(`output_manager.revert()`);
|
pyodide.runPython(`output_manager.revert()`);
|
||||||
@@ -143,8 +168,8 @@ export class BaseEvalElement extends HTMLElement {
|
|||||||
this.errorElement.style.removeProperty('display');
|
this.errorElement.style.removeProperty('display');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeClasses(this.errorElement, ['bg-red-200', 'p-2']);
|
|
||||||
}
|
}
|
||||||
|
removeClasses(this.errorElement, ['bg-red-200', 'p-2']);
|
||||||
|
|
||||||
this.postEvaluate();
|
this.postEvaluate();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -141,11 +141,6 @@ export class PyRepl extends BaseEvalElement {
|
|||||||
|
|
||||||
if (this.hasAttribute('output')) {
|
if (this.hasAttribute('output')) {
|
||||||
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
|
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
|
||||||
|
|
||||||
// in this case, the default output-mode is append, if hasn't been specified
|
|
||||||
if (!this.hasAttribute('output-mode')) {
|
|
||||||
this.setAttribute('output-mode', 'append');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (this.hasAttribute('std-out')) {
|
if (this.hasAttribute('std-out')) {
|
||||||
this.outputElement = document.getElementById(this.getAttribute('std-out'));
|
this.outputElement = document.getElementById(this.getAttribute('std-out'));
|
||||||
@@ -176,6 +171,13 @@ export class PyRepl extends BaseEvalElement {
|
|||||||
this.outputElement.hidden = false;
|
this.outputElement.hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preEvaluate(): void {
|
||||||
|
this.setOutputMode("replace");
|
||||||
|
if(!this.appendOutput) {
|
||||||
|
this.outputElement.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
postEvaluate(): void {
|
postEvaluate(): void {
|
||||||
this.outputElement.hidden = false;
|
this.outputElement.hidden = false;
|
||||||
this.outputElement.style.display = 'block';
|
this.outputElement.style.display = 'block';
|
||||||
@@ -189,8 +191,15 @@ export class PyRepl extends BaseEvalElement {
|
|||||||
const newPyRepl = document.createElement('py-repl');
|
const newPyRepl = document.createElement('py-repl');
|
||||||
newPyRepl.setAttribute('root', this.getAttribute('root'));
|
newPyRepl.setAttribute('root', this.getAttribute('root'));
|
||||||
newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString();
|
newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString();
|
||||||
|
|
||||||
|
if(this.hasAttribute('auto-generate')) {
|
||||||
newPyRepl.setAttribute('auto-generate', '');
|
newPyRepl.setAttribute('auto-generate', '');
|
||||||
this.removeAttribute('auto-generate');
|
this.removeAttribute('auto-generate');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.hasAttribute('output-mode')) {
|
||||||
|
newPyRepl.setAttribute('output-mode', this.getAttribute('output-mode'));
|
||||||
|
}
|
||||||
|
|
||||||
const addReplAttribute = (attribute: string) => {
|
const addReplAttribute = (attribute: string) => {
|
||||||
if (this.hasAttribute(attribute)) {
|
if (this.hasAttribute(attribute)) {
|
||||||
@@ -209,9 +218,10 @@ export class PyRepl extends BaseEvalElement {
|
|||||||
|
|
||||||
getSourceFromElement(): string {
|
getSourceFromElement(): string {
|
||||||
const sourceStrings = [
|
const sourceStrings = [
|
||||||
`output_manager.change("` + this.outputElement.id + `")`,
|
`output_manager.change(out="${this.outputElement.id}", append=True)`,
|
||||||
...this.editor.state.doc.toString().split('\n'),
|
...this.editor.state.doc.toString().split('\n'),
|
||||||
];
|
];
|
||||||
|
|
||||||
return sourceStrings.join('\n');
|
return sourceStrings.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,31 +107,22 @@ def format_mime(obj):
|
|||||||
class PyScript:
|
class PyScript:
|
||||||
loop = loop
|
loop = loop
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def write(element_id, value, append=False, exec_id=0):
|
|
||||||
"""Writes value to the element with id "element_id"""
|
|
||||||
console.log(f"APPENDING: {append} ==> {element_id} --> {value}")
|
|
||||||
if append:
|
|
||||||
child = document.createElement("div")
|
|
||||||
element = document.querySelector(f"#{element_id}")
|
|
||||||
if not element:
|
|
||||||
return
|
|
||||||
exec_id = exec_id or element.childElementCount + 1
|
|
||||||
element_id = child.id = f"{element_id}-{exec_id}"
|
|
||||||
element.appendChild(child)
|
|
||||||
|
|
||||||
element = document.getElementById(element_id)
|
|
||||||
html, mime_type = format_mime(value)
|
|
||||||
if mime_type in ("application/javascript", "text/html"):
|
|
||||||
script_element = document.createRange().createContextualFragment(html)
|
|
||||||
element.appendChild(script_element)
|
|
||||||
else:
|
|
||||||
element.innerHTML = html
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_until_complete(f):
|
def run_until_complete(f):
|
||||||
_ = loop.run_until_complete(f)
|
_ = loop.run_until_complete(f)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def write(element_id, value, append=False, exec_id=0):
|
||||||
|
"""Writes value to the element with id "element_id"""
|
||||||
|
Element(element_id).write(value=value, append=append)
|
||||||
|
console.warn(
|
||||||
|
dedent(
|
||||||
|
"""PyScript Deprecation Warning: PyScript.write is
|
||||||
|
marked as deprecated and will be removed sometime soon. Please, use
|
||||||
|
Element(<id>).write instead."""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Element:
|
class Element:
|
||||||
def __init__(self, element_id, element=None):
|
def __init__(self, element_id, element=None):
|
||||||
@@ -159,9 +150,25 @@ class Element:
|
|||||||
|
|
||||||
def write(self, value, append=False):
|
def write(self, value, append=False):
|
||||||
console.log(f"Element.write: {value} --> {append}")
|
console.log(f"Element.write: {value} --> {append}")
|
||||||
# TODO: it should be the opposite... pyscript.write should use the Element.write
|
|
||||||
# so we can consolidate on how we write depending on the element type
|
out_element_id = self.id
|
||||||
pyscript.write(self._id, value, append=append)
|
|
||||||
|
if append:
|
||||||
|
child = document.createElement("div")
|
||||||
|
exec_id = self.element.childElementCount + 1
|
||||||
|
out_element_id = child.id = f"{self.id}-{exec_id}"
|
||||||
|
self.element.appendChild(child)
|
||||||
|
|
||||||
|
out_element = document.querySelector(f"#{out_element_id}")
|
||||||
|
|
||||||
|
html, mime_type = format_mime(value)
|
||||||
|
if mime_type in ("application/javascript", "text/html"):
|
||||||
|
script_element = document.createRange().createContextualFragment(html)
|
||||||
|
out_element.appendChild(script_element)
|
||||||
|
else:
|
||||||
|
if html == "\n":
|
||||||
|
return
|
||||||
|
out_element.innerHTML = html
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
if hasattr(self.element, "value"):
|
if hasattr(self.element, "value"):
|
||||||
@@ -374,7 +381,7 @@ class OutputCtxManager:
|
|||||||
self.output_to_console = output_to_console
|
self.output_to_console = output_to_console
|
||||||
self._append = append
|
self._append = append
|
||||||
|
|
||||||
def change(self, out=None, err=None, output_to_console=True, append=True):
|
def change(self, out=None, output_to_console=True, append=True):
|
||||||
self._prev = self._out
|
self._prev = self._out
|
||||||
self._out = out
|
self._out = out
|
||||||
self.output_to_console = output_to_console
|
self.output_to_console = output_to_console
|
||||||
@@ -385,32 +392,37 @@ class OutputCtxManager:
|
|||||||
console.log("----> reverted")
|
console.log("----> reverted")
|
||||||
self._out = self._prev
|
self._out = self._prev
|
||||||
|
|
||||||
def write(self, txt):
|
def write(self, value):
|
||||||
console.log("writing to", self._out, txt, self._append)
|
console.log("writing to", self._out, value, self._append)
|
||||||
if self._out:
|
if self._out:
|
||||||
pyscript.write(self._out, txt, append=self._append)
|
Element(self._out).write(value, self._append)
|
||||||
|
|
||||||
if self.output_to_console:
|
if self.output_to_console:
|
||||||
console.log(self._out, txt)
|
console.log(self._out, value)
|
||||||
|
|
||||||
|
|
||||||
class OutputManager:
|
class OutputManager:
|
||||||
def __init__(self, out=None, err=None, output_to_console=True, append=True):
|
def __init__(self, out=None, err=None, output_to_console=True, append=True):
|
||||||
sys.stdout = self._out_manager = OutputCtxManager(
|
sys.stdout = self._out_manager = OutputCtxManager(
|
||||||
out, output_to_console, append
|
out=out, output_to_console=output_to_console, append=append
|
||||||
)
|
)
|
||||||
sys.stderr = self._err_manager = OutputCtxManager(
|
sys.stderr = self._err_manager = OutputCtxManager(
|
||||||
err, output_to_console, append
|
out=err, output_to_console=output_to_console, append=append
|
||||||
)
|
)
|
||||||
self.output_to_console = output_to_console
|
self.output_to_console = output_to_console
|
||||||
self._append = append
|
self._append = append
|
||||||
|
|
||||||
def change(self, out=None, err=None, output_to_console=True, append=True):
|
def change(self, out=None, err=None, output_to_console=True, append=True):
|
||||||
self._out_manager.change(out, output_to_console, append)
|
self._out_manager.change(
|
||||||
|
out=out, output_to_console=output_to_console, append=append
|
||||||
|
)
|
||||||
sys.stdout = self._out_manager
|
sys.stdout = self._out_manager
|
||||||
self._err_manager.change(err, output_to_console, append)
|
self._err_manager.change(
|
||||||
|
out=err, output_to_console=output_to_console, append=append
|
||||||
|
)
|
||||||
sys.stderr = self._err_manager
|
sys.stderr = self._err_manager
|
||||||
self.output_to_console = output_to_console
|
self.output_to_console = output_to_console
|
||||||
self.append = append
|
self._append = append
|
||||||
|
|
||||||
def revert(self):
|
def revert(self):
|
||||||
self._out_manager.revert()
|
self._out_manager.revert()
|
||||||
|
|||||||
Reference in New Issue
Block a user