From 6d3c9a22c5c2136e0ba597ca8bf2202e67c3376e Mon Sep 17 00:00:00 2001
From: Jeremy Tuloup <jeremy.tuloup@gmail.com>
Date: Tue, 1 Mar 2022 09:11:18 +0100
Subject: [PATCH] Add `jupyterlite-sphinx`

---
 docs/source/conf.py                      | 199 ++----------
 docs/source/examples/index.rst           |  18 --
 docs/source/examples/introduction.nblink |   3 -
 docs/source/ipylab.ipynb                 | 380 +++++++++++++++++++++++
 readthedocs.yml                          |  11 +-
 setup.cfg                                |   3 +-
 6 files changed, 409 insertions(+), 205 deletions(-)
 delete mode 100644 docs/source/examples/index.rst
 delete mode 100644 docs/source/examples/introduction.nblink
 create mode 100644 docs/source/ipylab.ipynb

diff --git a/docs/source/conf.py b/docs/source/conf.py
index 6166198e..7954fc12 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,194 +1,33 @@
-#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
-#
-# ipylab documentation build configuration file
-#
-# This file is execfile()d with the current directory set to its
-# containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-
-# -- General configuration ------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#
-# needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
 extensions = [
-    "sphinx.ext.autodoc",
-    "sphinx.ext.viewcode",
-    "sphinx.ext.intersphinx",
-    "sphinx.ext.napoleon",
-    "sphinx.ext.todo",
-    "nbsphinx",
+    'sphinx.ext.autodoc',
+    'sphinx.ext.napoleon',
+    'jupyterlite_sphinx'
 ]
 
-# Ensure our extension is available:
-import json
-import sys
-from os.path import dirname, join as pjoin
-
-docs = dirname(dirname(__file__))
-root = dirname(docs)
-sys.path.insert(0, root)
-sys.path.insert(0, pjoin(docs, "sphinxext"))
+templates_path = ['_templates']
 
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ["_templates"]
+# jupyterlite_config = "jupyterlite_config.json"
 
-# The suffix(es) of source filenames.
-# You can specify multiple suffix as a list of string:
-#
-# source_suffix = ['.rst', '.md']
-source_suffix = ".rst"
-
-# The master toctree document.
-master_doc = "index"
+master_doc = 'index'
+source_suffix = '.rst'
 
 # General information about the project.
-project = "ipylab"
-copyright = "2019, ipylab contributors"
-author = "ipylab contributors"
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-
-
-# get version from python package:
-import os
-
-here = os.path.dirname(__file__)
-repo = os.path.join(here, "..", "..")
-with open(os.path.join(repo, "package.json")) as f:
-    package = json.load(f)
-    _version_js = package["version"]
-
-# The short X.Y version.
-version = "%i.%i" % tuple(int(part) for part in _version_js.split("."))[:2]
-# The full version, including alpha/beta/rc tags.
-release = (
-    _version_js.replace("-alpha.", "a").replace("-beta.", "b").replace("-rc.", "rc")
-)
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#
-# This is also used if you do content translation via gettext catalogs.
-# Usually you set "language" from the command line for these cases.
-language = None
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = ["**.ipynb_checkpoints"]
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = "sphinx"
-
-# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = False
-
-
-# -- Options for HTML output ----------------------------------------------
-
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-#
-# html_theme_options = {}
+project = 'ipylab'
+author = 'ipylab contributors'
 
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ["_static"]
-
-
-# -- Options for HTMLHelp output ------------------------------------------
+exclude_patterns = []
+highlight_language = 'python'
+pygments_style = 'sphinx'
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = "ipylabdoc"
-
-
-# -- Options for LaTeX output ---------------------------------------------
-
-latex_elements = {
-    # The paper size ('letterpaper' or 'a4paper').
-    #
-    # 'papersize': 'letterpaper',
-    # The font size ('10pt', '11pt' or '12pt').
-    #
-    # 'pointsize': '10pt',
-    # Additional stuff for the LaTeX preamble.
-    #
-    # 'preamble': '',
-    # Latex figure (float) alignment
-    #
-    # 'figure_align': 'htbp',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title,
-#  author, documentclass [howto, manual, or own class]).
-latex_documents = [
-    (master_doc, "ipylab.tex", "ipylab Documentation", "ipylab contributors", "manual"),
-]
-
-
-# -- Options for manual page output ---------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [(master_doc, "ipylab", "ipylab Documentation", [author], 1)]
+html_theme = "pydata_sphinx_theme"
+htmlhelp_basename = 'ipylabdoc'
 
+html_theme_options = dict(
+    github_url='https://github.com/jtpio/ipylab'
+)
 
-# -- Options for Texinfo output -------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-#  dir menu entry, description, category)
-texinfo_documents = [
-    (
-        master_doc,
-        "ipylab",
-        "ipylab Documentation",
-        author,
-        "ipylab",
-        "Python bridge to JupyterLab",
-        "Miscellaneous",
-    ),
-]
-
-
-# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {"https://docs.python.org/": None}
-
-# Read The Docs
-# on_rtd is whether we are on readthedocs.org, this line of code grabbed from
-# docs.readthedocs.org
-on_rtd = os.environ.get("READTHEDOCS", None) == "True"
-
-if not on_rtd:  # only import and set the theme if we're building docs locally
-    import sphinx_rtd_theme
-
-    html_theme = "sphinx_rtd_theme"
-    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
-
-# otherwise, readthedocs.org uses their theme by default, so no need to specify it
-
+html_static_path = ['_static']
 
-# Uncomment this line if you have know exceptions in your included notebooks
-# that nbsphinx complains about:
-#
-nbsphinx_allow_errors = True  # exception ipstruct.py ipython_genutils
+autodoc_member_order = 'bysource'
diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst
deleted file mode 100644
index 26b2bd28..00000000
--- a/docs/source/examples/index.rst
+++ /dev/null
@@ -1,18 +0,0 @@
-
-Examples
-========
-
-This section contains several examples generated from Jupyter notebooks.
-The widgets have been embedded into the page for demonstrative pruposes.
-
-.. todo::
-
-    Add links to notebooks in examples folder similar to the initial
-    one. This is a manual step to ensure only those examples that
-    are suited for inclusion are used.
-
-
-.. toctree::
-    :glob:
-
-    *
diff --git a/docs/source/examples/introduction.nblink b/docs/source/examples/introduction.nblink
deleted file mode 100644
index 258bb0f0..00000000
--- a/docs/source/examples/introduction.nblink
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  "path": "../../../examples/introduction.ipynb"
-}
diff --git a/docs/source/ipylab.ipynb b/docs/source/ipylab.ipynb
new file mode 100644
index 00000000..41c7e650
--- /dev/null
+++ b/docs/source/ipylab.ipynb
@@ -0,0 +1,380 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Command Registry"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ipylab import JupyterFrontEnd\n",
+    "\n",
+    "app = JupyterFrontEnd()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "app.version"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## List all commands"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "app.commands.list_commands()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Create a new console"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "app.commands.execute('console:create', {\n",
+    "    'insertMode': 'split-right',\n",
+    "    'kernelPreference': {\n",
+    "        'shutdownOnClose': True,\n",
+    "    }\n",
+    "})"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Change the theme"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "app.commands.execute('apputils:change-theme', { 'theme': 'JupyterLab Dark' })"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Create a new terminal"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "app.commands.execute('terminal:create-new')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Ready event\n",
+    "\n",
+    "Some functionalities might require the `JupyterFrontEnd` widget to be ready on the frontend first.\n",
+    "\n",
+    "This is for example the case when listing all the available commands, or retrieving the version with `app.version`.\n",
+    "\n",
+    "The `on_ready` method can be used to register a callback that will be fired when the frontend is ready."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ipylab import JupyterFrontEnd\n",
+    "from ipywidgets import Output\n",
+    "\n",
+    "app = JupyterFrontEnd()\n",
+    "out = Output()\n",
+    "\n",
+    "def init():\n",
+    "    # show the first 5 commands\n",
+    "    cmds = app.commands.list_commands()[:5]\n",
+    "    out.append_stdout(cmds)\n",
+    "\n",
+    "app.on_ready(init)\n",
+    "out"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Or using `asyncio`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import asyncio\n",
+    "\n",
+    "app = JupyterFrontEnd()\n",
+    "\n",
+    "out = Output()\n",
+    "\n",
+    "async def init():\n",
+    "    await app.ready()\n",
+    "    cmds = app.commands.list_commands()[:5]\n",
+    "    out.append_stdout(cmds)\n",
+    "\n",
+    "asyncio.create_task(init())\n",
+    "out"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Add your own command\n",
+    "\n",
+    "Let's create a nice plot with `bqlot` and generate some random data.\n",
+    "\n",
+    "See https://github.com/bqplot/bqplot/blob/master/examples/Advanced%20Plotting/Animations.ipynb for more details."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy as np\n",
+    "\n",
+    "from bqplot import LinearScale, Lines, Bars, Axis, Figure\n",
+    "from ipywidgets import IntSlider"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "xs = LinearScale()\n",
+    "ys1 = LinearScale()\n",
+    "ys2 = LinearScale()\n",
+    "\n",
+    "x = np.arange(20)\n",
+    "y = np.cumsum(np.random.randn(20))\n",
+    "y1 = np.random.rand(20)\n",
+    "\n",
+    "line = Lines(x=x, y=y, scales={'x': xs, 'y': ys1}, colors=['magenta'], marker='square')\n",
+    "bar = Bars(x=x, y=y1, scales={'x': xs, 'y': ys2}, colorpadding=0.2, colors=['steelblue'])\n",
+    "\n",
+    "xax = Axis(scale=xs, label='x', grid_lines='solid')\n",
+    "yax1 = Axis(scale=ys1, orientation='vertical', tick_format='0.1f', label='y', grid_lines='solid')\n",
+    "yax2 = Axis(scale=ys2, orientation='vertical', side='right', tick_format='0.0%', label='y1', grid_lines='none')\n",
+    "\n",
+    "Figure(marks=[bar, line], axes=[xax, yax1, yax2], animation_duration=1000)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We now define a function to update the data."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def update_data():\n",
+    "    line.y = np.cumsum(np.random.randn(20))\n",
+    "    bar.y = np.random.rand(20)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "update_data()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This function will now be called when the JupyterLab command is executed."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "app.commands.add_command('update_data', execute=update_data, label=\"Update Data\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Execute it!"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "app.commands.execute('update_data')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The slider should now be moving and taking random values.\n",
+    "\n",
+    "Also the list of commands gets updated with the newly added command:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assert 'random' in app.commands.list_commands()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "That's great, but the command doesn't visually show up in the palette yet. So let's add it!"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Add the command to the palette"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ipylab.commands import CommandPalette"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "palette = CommandPalette()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "palette.add_item('update_data', 'Python Commands')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Open the command palette on the left side and the command should show now be visible."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Remove a command\n",
+    "\n",
+    "To remove a command that was previously added:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "app.commands.remove_command('update_data')"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.2"
+  },
+  "widgets": {
+   "application/vnd.jupyter.widget-state+json": {
+    "state": {},
+    "version_major": 2,
+    "version_minor": 0
+   }
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/readthedocs.yml b/readthedocs.yml
index ba61a334..56acd737 100644
--- a/readthedocs.yml
+++ b/readthedocs.yml
@@ -2,10 +2,15 @@ version: 2
 build:
   os: ubuntu-20.04
   tools:
-    python: mambaforge-4.10
+    python: "3.8"
+    nodejs: "14"
+
+sphinx:
+  configuration: docs/source/conf.py
+
 python:
   install:
   - method: pip
     path: .
-conda:
-  environment: docs/environment.yml
+    extra_requirements:
+      - docs
diff --git a/setup.cfg b/setup.cfg
index f854acc8..ee981aac 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -42,9 +42,10 @@ dev =
 docs =
    sphinx>=1.5
    recommonmark
-   sphinx_rtd_theme
    nbsphinx>=0.2.13,<0.4.0
    jupyter_sphinx
+   jupyterlite-sphinx
    nbsphinx-link
+   pydata-sphinx-theme
    pytest_check_links
    pypandoc