diff --git a/docs/insiders/changelog.md b/docs/insiders/changelog.md
index d359ee4..ae3267b 100644
--- a/docs/insiders/changelog.md
+++ b/docs/insiders/changelog.md
@@ -2,6 +2,6 @@
## Markdown Exec Insiders
-### 1.0.0 April 22, 2023 { id="1.0.0" }
+### 1.0.0 April 26, 2023 { id="1.0.0" }
-- Release first Insiders version
+- Add a [`pyodide` fence](../usage/pyodide/)
diff --git a/docs/insiders/goals.yml b/docs/insiders/goals.yml
index 896b924..f6e21cd 100644
--- a/docs/insiders/goals.yml
+++ b/docs/insiders/goals.yml
@@ -1 +1,7 @@
-goals: {}
+goals:
+ 500:
+ name: PlasmaVac User Guide
+ features:
+ - name: Pyodide fence
+ ref: ../usage/pyodide/
+ since: 2023/04/26
diff --git a/docs/usage/pyodide.md b/docs/usage/pyodide.md
new file mode 100644
index 0000000..d833ef8
--- /dev/null
+++ b/docs/usage/pyodide.md
@@ -0,0 +1,85 @@
+# Pyodide
+
+[:octicons-heart-fill-24:{ .pulse } Sponsors only](../../insiders){ .insiders } —
+[:octicons-tag-24: Insiders 1.0.0](../../insiders/changelog#1.0.0)
+
+This special `pyodide` fence uses [Pyodide](https://pyodide.org), [Ace](https://ace.c9.io/)
+and [Highlight.js](https://highlightjs.org/) to render an interactive Python editor.
+Everything runs on the client side. The first time Pyodide is loaded by the browser
+can be a bit long, but then it will be cached and the next time you load the page
+it will be much faster.
+
+Click the "Run" button in the top-right corner, or hit ++ctrl+enter++ to run the code.
+You can install packages with Micropip:
+
+````md exec="1" source="tabbed-right" tabs="Markdown|Rendered"
+```pyodide
+import micropip
+
+print("Installing cowsay...")
+await micropip.install("cowsay")
+print("done!")
+```
+````
+
+Then you can import and use the packages you installed:
+
+````md exec="1" source="tabbed-right" tabs="Markdown|Rendered"
+```pyodide
+import cowsay
+cowsay.cow("Hello World")
+```
+````
+
+Packages installed with Micropip are cached by the browser as well,
+making future installations much faster.
+
+## Sessions
+
+Editors with the same session share the same `globals()` dictionary,
+so you can reuse variables, classes, imports, etc., from another editor
+within the same session. This is why you can import `cowsay` in this editor,
+given you actually installed it in the first. Sessions are ephemeral:
+everything is reset when reloading the page. This means you cannot persist
+sessions across multiple pages. Try refreshing your page
+and running the code of the second editor: you should get a ModuleNotFoundError.
+
+To use other sessions, simply pass the `session="name"` option to the code block:
+
+````md exec="1" source="tabbed-right" tabs="Markdown|Rendered"
+```pyodide session="something"
+something = "hello"
+```
+````
+
+Now lets print it in another editor with the same session:
+
+````md exec="1" source="tabbed-right" tabs="Markdown|Rendered"
+```pyodide session="something"
+print(something)
+```
+````
+
+And in another editor with the default session:
+
+````md exec="1" source="tabbed-right" tabs="Markdown|Rendered"
+```pyodide
+print(something)
+```
+````
+
+## Pre-installing packages
+
+In your own documentation pages, you might not want to add
+`import micropip; await micropip.install("your-package")`
+to every editor to show how to use your package. In this case,
+you can use the `install` option to pre-install packages.
+The option takes a list of comma-separated package distribution names:
+
+````md exec="1" source="tabbed-right" tabs="Markdown|Rendered"
+```pyodide install="griffe,dependenpy"
+import griffe
+import dependenpy
+print("OK!")
+```
+````
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
index e0e7761..736c7ba 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -16,6 +16,7 @@ nav:
- Usage:
- usage/index.md
- Python: usage/python.md
+ - Pyodide: usage/pyodide.md
- Shell: usage/shell.md
- Tree: usage/tree.md
- Gallery: gallery.md
@@ -93,6 +94,7 @@ markdown_extensions:
check_paths: true
base_path: [docs/snippets, "."]
- pymdownx.highlight
+- pymdownx.keys
- pymdownx.superfences:
custom_fences:
- name: mermaid
diff --git a/src/markdown_exec/__init__.py b/src/markdown_exec/__init__.py
index 2245bd0..6d09f73 100644
--- a/src/markdown_exec/__init__.py
+++ b/src/markdown_exec/__init__.py
@@ -19,6 +19,7 @@
from markdown_exec.formatters.console import _format_console
from markdown_exec.formatters.markdown import _format_markdown
from markdown_exec.formatters.pycon import _format_pycon
+from markdown_exec.formatters.pyodide import _format_pyodide
from markdown_exec.formatters.python import _format_python
from markdown_exec.formatters.sh import _format_sh
from markdown_exec.formatters.tree import _format_tree
@@ -34,6 +35,7 @@
"py": _format_python,
"python": _format_python,
"pycon": _format_pycon,
+ "pyodide": _format_pyodide,
"sh": _format_sh,
"tree": _format_tree,
}
@@ -62,7 +64,7 @@ def validator(
Success or not.
"""
exec_value = _to_bool(inputs.pop("exec", "no"))
- if language != "tree" and not exec_value:
+ if language not in {"tree", "pyodide"} and not exec_value:
return False
id_value = inputs.pop("id", "")
id_prefix_value = inputs.pop("idprefix", None)
diff --git a/src/markdown_exec/formatters/pyodide.py b/src/markdown_exec/formatters/pyodide.py
new file mode 100644
index 0000000..408389a
--- /dev/null
+++ b/src/markdown_exec/formatters/pyodide.py
@@ -0,0 +1,60 @@
+"""Formatter for creating a Pyodide interactive editor."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+if TYPE_CHECKING:
+ from markdown import Markdown
+
+play_emoji = ''
+clear_emoji = ''
+
+template = """
+
+
+
+
+
+
+
+
+Editor (session: %(session)s)%(play_emoji)s Run
+