diff --git a/examples/menu.ipynb b/examples/menu.ipynb
index c08382c..c5dc59e 100644
--- a/examples/menu.ipynb
+++ b/examples/menu.ipynb
@@ -204,8 +204,8 @@
"metadata": {},
"outputs": [],
"source": [
- "async def show_id(active_widget: ipylab.ShellConnection):\n",
- " id_ = await active_widget.get_property(\"id\")\n",
+ "async def show_id(current_widget: ipylab.ShellConnection):\n",
+ " id_ = await current_widget.get_property(\"id\")\n",
" await app.dialog.show_dialog(\"Show id\", f\"Widget id is {id_}\")\n",
"\n",
"\n",
diff --git a/examples/plugins.ipynb b/examples/plugins.ipynb
index f04c8f6..9058dd4 100644
--- a/examples/plugins.ipynb
+++ b/examples/plugins.ipynb
@@ -117,10 +117,9 @@
"Then try.\n",
"```python\n",
"\n",
- "test # Should print 'Test' as defined in the 'namespace_objects' plugin.\n",
- "\n",
"# Now lets load a different namespace is available.\n",
"app.activate_namespace('test')\n",
+ "test # Should print 'Test' as defined in the 'namespace_objects' plugin.\n",
"dir()\n",
"\n",
"# To switch back use\n",
@@ -269,7 +268,7 @@
"\n",
"#### Command\n",
"\n",
- "The ipylab kernel can be started/re-started with the command 'Start ipylab kernel' (`Ctrl c` -> 'Start ipylab kernel').\n",
+ "The ipylab kernel can be started/re-started with the command 'Start ipylab kernel' (`Ctrl c` -> 'Start or restart ipylab kernel').\n",
"\n",
"#### Configure\n",
"\n",
diff --git a/examples/sessions.ipynb b/examples/sessions.ipynb
index 7ce263c..3558fa5 100644
--- a/examples/sessions.ipynb
+++ b/examples/sessions.ipynb
@@ -47,14 +47,7 @@
"metadata": {},
"outputs": [],
"source": [
- "app.all_sessions"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Show current session"
+ "t = app.sessions.get_running()"
]
},
{
@@ -63,33 +56,14 @@
"metadata": {},
"outputs": [],
"source": [
- "app.current_session"
+ "t.result()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Example: Create Console with current session\n",
- "The following two commands should both create a console panel sharing the same `session` as the current notebook."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "app.commands.execute(\"console:create\", app.current_session)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Create a new Notebook\n",
- "\n",
- "Create a new notebook and use the connection to interact with the notebook directly."
+ "## Show current session"
]
},
{
@@ -98,7 +72,7 @@
"metadata": {},
"outputs": [],
"source": [
- "t = app.commands.execute(\"notebook:create-new\", transform=ipylab.Transform.connection)"
+ "t = app.sessions.get_current()"
]
},
{
@@ -107,24 +81,16 @@
"metadata": {},
"outputs": [],
"source": [
- "nb = t.result()\n",
- "nb # A Connection to the notebook."
+ "session = t.result()\n",
+ "session"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "We can use the connection to the notebook to do various actions. Lets list the attributes of the context."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "nb.execute_method(\"context.rename\", \"My renamed notebook.ipynb\")"
+ "## Example: Create Console with current session\n",
+ "This command creates a new console sharing the same `session` as the current notebook."
]
},
{
@@ -133,7 +99,7 @@
"metadata": {},
"outputs": [],
"source": [
- "nb.close()"
+ "app.commands.execute(\"console:create\", session)"
]
}
],
@@ -153,7 +119,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.12.7"
+ "version": "3.11.10"
}
},
"nbformat": 4,
diff --git a/ipylab/commands.py b/ipylab/commands.py
index c5cb722..c788fa9 100644
--- a/ipylab/commands.py
+++ b/ipylab/commands.py
@@ -116,14 +116,14 @@ def add(
Special args
------------
- * active_widget: ShellConnection
+ * current_widget: ShellConnection
* ref: ShellConnection
Include in the argument list of the function to have the value provided when the command
is called.
- active_widget:
- This is a ShellConnection to the Jupyterlab defined active widget.
+ current_widget:
+ This is a ShellConnection to the Jupyterlab defined current widget.
For the command to appear in the context menu non-ipylab widgets, the appropriate selector
should be used. see: https://jupyterlab.readthedocs.io/en/stable/developer/css.html#commonly-used-css-selectors
Selectors:
@@ -131,10 +131,10 @@ def add(
* Main area: '.jp-Activity'
ref:
- This is a ShellConnection to the Ipylab active widget.
+ This is a ShellConnection to the Ipylab current widget.
The associated widget/panel is then accessible by `ref.widget`.
- Tip: This is can be used in context menus to perform actions specific to the active widget
+ Tip: This is can be used in context menus to perform actions specific to the current widget
in the shell.
"""
cid = CommandPalletItemConnection.to_cid(command, category)
@@ -198,12 +198,12 @@ async def _execute_for_frontend(self, payload: dict, buffers: list):
args = conn.args | (payload.get("args") or {}) | {"buffers": buffers}
# Shell connections
- cids = {"active_widget": payload["cid1"], "ref": payload["cid2"]}
+ cids = {"current_widget": payload["cid1"], "ref": payload["cid2"]}
glbls = ipylab.app.get_namespace(conn.namespace_name)
kwgs = {}
for n, p in inspect.signature(cmd).parameters.items():
- if n in ["active_widget", "ref"] and cids[n]:
+ if n in ["current_widget", "ref"] and cids[n]:
kwgs[n] = ShellConnection(cids[n])
await kwgs[n].ready()
elif n in args:
diff --git a/ipylab/connection.py b/ipylab/connection.py
index c256a4a..81e8efe 100644
--- a/ipylab/connection.py
+++ b/ipylab/connection.py
@@ -14,6 +14,7 @@
from ipylab.ipylab import Ipylab
if TYPE_CHECKING:
+ from asyncio import Task
from collections.abc import Generator
from typing import Literal, Self, overload
@@ -169,3 +170,7 @@ def activate(self):
"Activate the connected widget in the shell."
return self.operation("activate")
+
+ def get_session(self) -> Task[dict]:
+ """Get the session of the connected widget."""
+ return self.operation("getSession")
diff --git a/ipylab/dialog.py b/ipylab/dialog.py
index ee529bd..99db1bd 100644
--- a/ipylab/dialog.py
+++ b/ipylab/dialog.py
@@ -117,7 +117,8 @@ def show_dialog(
see: https://jupyterlab.readthedocs.io/en/stable/api/functions/apputils.showDialog.html
source: https://jupyterlab.readthedocs.io/en/stable/extension/ui_helpers.html#generic-dialog
"""
- kwgs["toLuminoWidget"] = ["body"] if isinstance(body, Widget) else []
+ if isinstance(body, Widget) and "toLuminoWidget" not in kwgs:
+ kwgs["toLuminoWidget"] = ["body"]
return self.operation("showDialog", _combine(options, title=title, body=body), **kwgs)
def show_error_message(
diff --git a/ipylab/jupyterfrontend.py b/ipylab/jupyterfrontend.py
index 1740e58..c1113ac 100644
--- a/ipylab/jupyterfrontend.py
+++ b/ipylab/jupyterfrontend.py
@@ -53,10 +53,7 @@ class App(Ipylab):
_model_name = Unicode("JupyterFrontEndModel").tag(sync=True)
ipylab_base = IpylabBase(Obj.IpylabModel, "app").tag(sync=True)
version = Unicode(read_only=True).tag(sync=True)
- current_widget_id = Unicode(read_only=True).tag(sync=True)
logger_level = UseEnum(LogLevel, read_only=True, default_value=LogLevel.warning).tag(sync=True)
- current_session = Dict(read_only=True).tag(sync=True)
- all_sessions = Tuple(read_only=True).tag(sync=True)
vpath = Unicode(read_only=True).tag(sync=True)
per_kernel_widget_manager_detected = Bool(read_only=True).tag(sync=True)
@@ -174,9 +171,9 @@ async def _evaluate(self, options: dict, buffers: list):
self.get_namespace(namespace_name, glbls)
return {"payload": glbls.get("payload"), "buffers": buffers}
- def _context_open_console(self, ref: ShellConnection, active_widget: ShellConnection):
+ def _context_open_console(self, ref: ShellConnection, current_widget: ShellConnection):
"This command is provided for the 'autostart' context menu."
- return self.open_console(objects={"ref": ref, "active_widget": active_widget})
+ return self.open_console(objects={"ref": ref, "current_widget": current_widget})
def open_console(
self,
diff --git a/ipylab/sessions.py b/ipylab/sessions.py
index c954c0f..4fb8657 100644
--- a/ipylab/sessions.py
+++ b/ipylab/sessions.py
@@ -3,9 +3,16 @@
from __future__ import annotations
+from typing import TYPE_CHECKING
+
+from traitlets import Unicode
+
from ipylab.common import Obj
from ipylab.ipylab import Ipylab, IpylabBase
+if TYPE_CHECKING:
+ from asyncio import Task
+
class SessionManager(Ipylab):
"""
@@ -14,11 +21,16 @@ class SessionManager(Ipylab):
SINGLE = True
+ _model_name = Unicode("SessionManagerModel", help="Name of the model.", read_only=True).tag(sync=True)
ipylab_base = IpylabBase(Obj.IpylabModel, "app.serviceManager.sessions").tag(sync=True)
- def refresh_running(self):
- """Force a call to refresh running sessions."""
- return self.execute_method("refreshRunning")
+ def get_running(self, *, refresh=True) -> Task[dict]:
+ "Get a dict of running sessions."
+ return self.operation("getRunning", {"refresh": refresh})
+
+ def get_current(self):
+ "Get the session of the current widget in the shell."
+ return self.operation("getCurrentSession")
def stop_if_needed(self, path):
"""
diff --git a/ipylab/shell.py b/ipylab/shell.py
index 98ca16c..dfccd45 100644
--- a/ipylab/shell.py
+++ b/ipylab/shell.py
@@ -4,14 +4,14 @@
from __future__ import annotations
import inspect
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Unpack
from ipywidgets import DOMWidget, TypedTuple, Widget
from traitlets import Container, Instance, Unicode
import ipylab
from ipylab import Area, InsertMode, Ipylab, ShellConnection, Transform, pack
-from ipylab.common import Obj
+from ipylab.common import IpylabKwgs, Obj, TaskHookType
from ipylab.ipylab import IpylabBase
if TYPE_CHECKING:
@@ -39,6 +39,7 @@ class Shell(Ipylab):
_model_name = Unicode("ShellModel", help="Name of the model.", read_only=True).tag(sync=True)
ipylab_base = IpylabBase(Obj.IpylabModel, "app.shell").tag(sync=True)
+ current_widget_id = Unicode(read_only=True).tag(sync=True)
connections: Container[tuple[ShellConnection, ...]] = TypedTuple(trait=Instance(ShellConnection))
@@ -53,10 +54,11 @@ def add(
ref: ShellConnection | None = None,
options: dict | None = None,
vpath: str | dict[Literal["title"], str] = "",
+ hooks: TaskHookType = None,
**args,
) -> Task[ShellConnection]:
"""
- Add a widget or evaluation to the shell.
+ Add a widget to the shell.
obj
---
@@ -66,7 +68,8 @@ def add(
**Only relevant for 'evaluate'**
The 'virtual' path for the app. A new kernel will be created if a session
doesn't exist with the same path.
- If a dict is provided, a text_dialog will be used to obtain the vpath.
+ If a dict is provided, a text_dialog will be used to obtain the vpath with the
+ hook `vpath_getter`.
Note:
The result (payload) of evaluate must be a Widget with a view and NOT a ShellConnection.
@@ -81,7 +84,7 @@ def add(
app.shell.add("ipylab.Panel([ipw.HTML('
Test')])", vpath="test")
```
"""
- hooks: TaskHooks = {"add_to_tuple_fwd": [(self, "connections")]}
+ hooks_: TaskHooks = {"add_to_tuple_fwd": [(self, "connections")]}
args["options"] = {
"activate": activate,
"mode": InsertMode(mode),
@@ -105,9 +108,9 @@ def add(
if c.widget is obj:
args["cid"] = c.cid
break
- hooks["trait_add_fwd"] = [("widget", obj)]
+ hooks_["trait_add_fwd"] = [("widget", obj)]
if isinstance(obj, ipylab.Panel):
- hooks["add_to_tuple_fwd"].append((obj, "connections"))
+ hooks_["add_to_tuple_fwd"].append((obj, "connections"))
args["ipy_model"] = obj.model_id
if isinstance(obj, DOMWidget):
obj.add_class(ipylab.app.selector.removeprefix("."))
@@ -124,11 +127,11 @@ async def add_to_shell() -> ShellConnection:
else:
args["vpath"] = vpath or ipylab.app.vpath
if args["vpath"] != ipylab.app.vpath:
- hooks["trait_add_fwd"] = [("auto_dispose", False)]
+ hooks_["trait_add_fwd"] = [("auto_dispose", False)]
else:
args["vpath"] = ipylab.app.vpath
- return await self.operation("addToShell", {"args": args}, transform=Transform.connection)
+ return await self.operation("addToShell", {"args": args}, transform=Transform.connection, hooks=hooks_)
return self.to_task(add_to_shell(), "Add to shell", hooks=hooks)
@@ -143,3 +146,12 @@ def collapse_left(self):
def collapse_right(self):
return self.execute_method("collapseRight")
+
+ def connect_to_widget(self, widget_id="", **kwgs: Unpack[IpylabKwgs]) -> Task[ShellConnection]:
+ "Make a connection to a widget in the shell (see also `get_widget_ids`)."
+ kwgs["transform"] = Transform.connection
+ return self.operation("getWidget", {"id": widget_id}, **kwgs)
+
+ def list_widget_ids(self, **kwgs: Unpack[IpylabKwgs]) -> Task[dict[Area, list[str]]]:
+ "Get a mapping of Areas to a list of widget ids in that area in the shell."
+ return self.operation("getWidgetIds", **kwgs)
diff --git a/src/widget.ts b/src/widget.ts
index 45b2265..2abf3cf 100644
--- a/src/widget.ts
+++ b/src/widget.ts
@@ -12,7 +12,7 @@ import { PanelModel, PanelView } from './widgets/panel';
import { ShellModel } from './widgets/shell';
import { SplitPanelModel, SplitPanelView } from './widgets/split_panel';
import { TitleModel } from './widgets/title';
-
+import { SessionManagerModel } from './widgets/sessions';
export {
CommandRegistryModel,
ConnectionModel,
@@ -24,6 +24,7 @@ export {
NotificationManagerModel,
PanelModel,
PanelView,
+ SessionManagerModel,
ShellConnectionModel,
ShellModel,
SplitPanelModel,
diff --git a/src/widgets/connection.ts b/src/widgets/connection.ts
index 6cd30e0..3cc9138 100644
--- a/src/widgets/connection.ts
+++ b/src/widgets/connection.ts
@@ -6,19 +6,12 @@ import { Signal } from '@lumino/signaling';
import { IpylabModel } from './ipylab';
import { IObservableDisposable } from '@lumino/disposable';
import { Widget } from '@lumino/widgets';
-import { ILabShell } from '@jupyterlab/application';
+
/**
- * Provides a connection to an object using a unique 'cid'.
- *
+ * ConnectionModel provides a connection to an object using a unique 'cid'.
*
- * An object must be registered static method `ConnectionModel.registerConnection`
- * to establish the connection. The base class expects the object to be
- * register before it is created. Subclasses can indicate
- * by using a 'pending' promise. The
- * The 'cid' can generated in Python first, or generated in the Frontend if a 'cid'
- * isn't provided.
- *
- * The object is set to `this.base`.
+ * The object to be referenced must first be registered static method
+ * `ConnectionModel.registerConnection`.
*/
export class ConnectionModel extends IpylabModel {
/**
@@ -141,7 +134,7 @@ export class ConnectionModel extends IpylabModel {
const cls =
obj instanceof Widget &&
obj.id &&
- ConnectionModel.getLuminoWidgetFromShell(obj.id)
+ ConnectionModel.ShellModel.getLuminoWidgetFromShell(obj.id)
? 'ShellConnection'
: 'Connection';
const cid = ConnectionModel.new_cid(cls);
@@ -155,29 +148,14 @@ export class ConnectionModel extends IpylabModel {
}
/**
- * Get the lumino widget from the shell using its id.
- *
- * @param id
- * @returns
+ * Get the session associated with the lumino widget if it has one.
*/
- static getLuminoWidgetFromShell(id: string): Widget | null {
- for (const area of [
- 'main',
- 'header',
- 'top',
- 'menu',
- 'left',
- 'right',
- 'bottom'
- ]) {
- for (const widget of IpylabModel.labShell.widgets(
- area as ILabShell.Area
- )) {
- if (widget.id === id) {
- return widget;
- }
- }
+ static async getSession(widget: Widget) {
+ const path = (widget as any)?.ipylabSettings?.vpath;
+ if (path) {
+ return await ConnectionModel.sessionManager.findByPath(path);
}
+ return (widget as any)?.sessionContext?.session?.model ?? {};
}
static new_cid(cls: string): string {
@@ -186,13 +164,14 @@ export class ConnectionModel extends IpylabModel {
return `${_PREFIX}${cls}${_SEP}${UUID.uuid4()}`;
}
+
// 'cid' is used by BackboneJS so we use cid_ here.
cid_: string;
readonly isConnectionModel = true;
}
/**
- * A connection for widgets in the Shell.
+ * A connection to widgets in the Shell.
*/
export class ShellConnectionModel extends ConnectionModel {
/*
@@ -223,6 +202,8 @@ export class ShellConnectionModel extends ConnectionModel {
switch (op) {
case 'activate':
return IpylabModel.app.shell.activateById(this.base.id);
+ case 'getSession':
+ return ShellConnectionModel.getSession(this.base);
default:
return await super.operation(op, payload);
}
diff --git a/src/widgets/frontend.ts b/src/widgets/frontend.ts
index fc5ca26..3d551b2 100644
--- a/src/widgets/frontend.ts
+++ b/src/widgets/frontend.ts
@@ -31,13 +31,6 @@ export class JupyterFrontEndModel extends IpylabModel {
async ipylabInit(base: any = null) {
this.set('version', JFEM.app.version);
this.set('per_kernel_widget_manager_detected', JFEM.PER_KERNEL_WM);
- JFEM.sessionManager.runningChanged.connect(this.updateAllSessions, this);
- if (JFEM.labShell) {
- JFEM.labShell.currentChanged.connect(this.updateSessionInfo, this);
- JFEM.labShell.activeChanged.connect(this.updateSessionInfo, this);
- this.updateSessionInfo();
- }
- this.updateAllSessions();
this.set('logger_level', this.logger.level);
await super.ipylabInit(base);
if (!Private.vpathTojfem.has(this.vpath)) {
@@ -49,9 +42,6 @@ export class JupyterFrontEndModel extends IpylabModel {
close(comm_closed?: boolean): Promise {
Private.jfems.delete(this.kernelId);
Private.vpathTojfem.delete(this.vpath);
- JFEM.labShell.currentChanged.disconnect(this.updateSessionInfo, this);
- JFEM.labShell.activeChanged.disconnect(this.updateSessionInfo, this);
- JFEM.sessionManager.runningChanged.disconnect(this.updateAllSessions, this);
this.logger.stateChanged.disconnect(this.loggerStateChanged as any, this);
return super.close(comm_closed);
}
@@ -77,20 +67,6 @@ export class JupyterFrontEndModel extends IpylabModel {
return vpath;
}
- private updateSessionInfo(): void {
- const currentWidget = JFEM.app.shell.currentWidget as any;
- const current_session = currentWidget?.sessionContext?.session?.model ?? {};
- if (this.get('current_widget_id') !== currentWidget?.id) {
- this.set('current_widget_id', currentWidget?.id ?? '');
- this.set('current_session', current_session);
- this.save_changes();
- }
- }
- private updateAllSessions(): void {
- this.set('all_sessions', Array.from(JFEM.sessionManager.running()));
- this.save_changes();
- }
-
private loggerStateChanged(sender: ILogger, change: IStateChange): void {
if (this.get('logger_level') !== this.logger.level) {
this.set('logger_level', this.logger.level);
@@ -142,32 +118,14 @@ export class JupyterFrontEndModel extends IpylabModel {
}
let kernel: Kernel.IKernelConnection;
Private.vpathTojfem.set(vpath, new PromiseDelegate());
- await IpylabModel.app.serviceManager.ready;
- await IpylabModel.sessionManager.ready;
+ await IpylabModel.sessionManager.refreshRunning();
const model = await IpylabModel.sessionManager.findByPath(vpath);
if (model) {
kernel = IpylabModel.app.serviceManager.kernels.connectTo({
model: model.kernel
});
} else {
- const sessionContext = new SessionContext({
- sessionManager: IpylabModel.sessionManager,
- specsManager: IpylabModel.app.serviceManager.kernelspecs,
- path: vpath,
- name: vpath,
- type: 'console',
- kernelPreference: { language: 'python' }
- });
- await sessionContext.initialize();
- if (!sessionContext.isReady) {
- await new SessionContextDialogs({
- translator: IpylabModel.translator
- }).selectKernel(sessionContext!);
- }
- if (!sessionContext.isReady) {
- sessionContext.dispose();
- throw new Error('Cancelling because a kernel was not provided');
- }
+ const sessionContext = await JFEM.newSessionContext(vpath);
kernel = sessionContext.session.kernel;
}
// Relies on per-kernel widget manager.
@@ -191,6 +149,34 @@ export class JupyterFrontEndModel extends IpylabModel {
});
}
+ /**
+ * Create a new session context for vpath.
+ *
+ * This will automatically starting a new kernel if a session path matching
+ * vpath isn't found.
+ */
+ static async newSessionContext(vpath: string) {
+ const sessionContext = new SessionContext({
+ sessionManager: IpylabModel.sessionManager,
+ specsManager: IpylabModel.app.serviceManager.kernelspecs,
+ path: vpath,
+ name: vpath,
+ type: 'console',
+ kernelPreference: { language: 'python' }
+ });
+ await sessionContext.initialize();
+ if (!sessionContext.isReady) {
+ await new SessionContextDialogs({
+ translator: IpylabModel.translator
+ }).selectKernel(sessionContext!);
+ }
+ if (!sessionContext.isReady) {
+ sessionContext.dispose();
+ throw new Error('Cancelling because a kernel was not provided');
+ }
+ return sessionContext;
+ }
+
/**
* Get the WidgetModel
*
@@ -287,10 +273,11 @@ class IpylabContext {
// this.contentsModel.path = path;
}
readonly path: string;
- ready = new Promise(resolve => resolve(null));
- pathChanged = new Signal(this);
+ ready = new Promise(resolve => resolve(null));
+ pathChanged: Signal = new Signal(this);
model: object = { stateChanged: new Signal(this) };
localPath = '';
+ async rename(newName: string) {}
}
/**
diff --git a/src/widgets/ipylab.ts b/src/widgets/ipylab.ts
index 6c71d4f..16feafd 100644
--- a/src/widgets/ipylab.ts
+++ b/src/widgets/ipylab.ts
@@ -29,12 +29,13 @@ import {
} from '../utils';
import { MODULE_NAME, MODULE_VERSION } from '../version';
import type { ConnectionModel } from './connection';
-import { JupyterFrontEndModel } from './frontend';
-
-// Determine if the per kernel widget manager is available
+import type { JupyterFrontEndModel } from './frontend';
+import type { ShellModel } from './shell';
/**
- * Base model for common features
+ * Base model for Ipylab.
+ *
+ * Subclass as required but can also be used directly.
*/
export class IpylabModel extends WidgetModel {
/**
@@ -371,6 +372,9 @@ export class IpylabModel extends WidgetModel {
if (obj?.dispose) {
return { cid: IpylabModel.ConnectionModel.get_cid(obj, true) };
}
+ if (typeof obj?.iterator === 'function') {
+ return Array.from(obj);
+ }
return await obj;
case 'null':
return null;
@@ -541,6 +545,7 @@ export class IpylabModel extends WidgetModel {
static tracker = new WidgetTracker({ namespace: 'ipylab' });
static JFEM: typeof JupyterFrontEndModel;
static ConnectionModel: typeof ConnectionModel;
+ static ShellModel: typeof ShellModel;
static Notification = Notification;
static PER_KERNEL_WM = Boolean((KernelWidgetManager as any)?.getManager);
}
diff --git a/src/widgets/sessions.ts b/src/widgets/sessions.ts
new file mode 100644
index 0000000..dd8c120
--- /dev/null
+++ b/src/widgets/sessions.ts
@@ -0,0 +1,34 @@
+// Copyright (c) ipylab contributors
+// Distributed under the terms of the Modified BSD License.
+
+// SessionManager exposes `app.serviceManager.sessions` to user python kernel
+
+import { IpylabModel } from './ipylab';
+
+/**
+ * The model for a Session Manager
+ */
+export class SessionManagerModel extends IpylabModel {
+ /**
+ * The default attributes.
+ */
+ defaults(): any {
+ return { ...super.defaults(), _model_name: SessionManagerModel };
+ }
+
+ async operation(op: string, payload: any): Promise {
+ switch (op) {
+ case 'getCurrentSession':
+ return await SessionManagerModel.ConnectionModel.getSession(
+ IpylabModel.labShell.currentWidget
+ );
+ case 'getRunning':
+ if (payload.refresh) {
+ await IpylabModel.sessionManager.refreshRunning();
+ }
+ return Array.from(IpylabModel.sessionManager.running());
+ default:
+ return await super.operation(op, payload);
+ }
+ }
+}
diff --git a/src/widgets/shell.ts b/src/widgets/shell.ts
index 8d9c0b9..6f4cec8 100644
--- a/src/widgets/shell.ts
+++ b/src/widgets/shell.ts
@@ -5,6 +5,17 @@ import { DocumentWidget } from '@jupyterlab/docregistry';
import { UUID } from '@lumino/coreutils';
import { Widget } from '@lumino/widgets';
import { IpylabModel } from './ipylab';
+import { ILabShell } from '@jupyterlab/application';
+
+const AREAS: Array = [
+ 'main',
+ 'left',
+ 'right',
+ 'header',
+ 'top',
+ 'menu',
+ 'bottom'
+];
export class ShellModel extends IpylabModel {
/**
@@ -14,10 +25,35 @@ export class ShellModel extends IpylabModel {
return { ...super.defaults(), _model_name: 'ShellModel' };
}
+ setReady(): void {
+ this.base.currentChanged.connect(this._currentChanged, this);
+ super.setReady();
+ }
+
+ _currentChanged() {
+ const id = this.base.currentWidget?.id;
+ if (id && id !== this.get('current_widget_id')) {
+ this.set('current_widget_id', id);
+ this.save_changes();
+ }
+ }
+
+ close(comm_closed?: boolean): Promise {
+ this.base.currentChanged.disconnect(this._currentChanged, this);
+ return super.close(comm_closed);
+ }
+
async operation(op: string, payload: any): Promise {
switch (op) {
case 'addToShell':
return await ShellModel.addToShell(payload.args);
+ case 'getWidget':
+ if (!payload.id) {
+ return this.base.currentWidget;
+ }
+ return ShellModel.getLuminoWidgetFromShell(payload.id);
+ case 'getWidgetIds':
+ return ShellModel.listWidgetIds();
default:
return await super.operation(op, payload);
}
@@ -49,14 +85,15 @@ export class ShellModel extends IpylabModel {
/**
* Add a widget to the application shell.
*
- * This function can handle ipywidgets and native Widgets and be used to move
- * widgets about the shell.
+ * This function can handle ipywidgets and native Widgets and be used to
+ * move widgets about the shell.
*
- * Ipywidgets are added to a tracker enabling restoration from a
- * running kernel such as page refreshing and switching workspaces.
+ * Ipywidgets are added to a tracker enabling restoration from a running
+ * kernel such as page refreshing and switching workspaces.
*
* Generative widget creation is supported with 'evaluate' using the same
- * code as 'evalute'. The evaluated code MUST return a widget with a view to be valid.
+ * code as 'evalute'. The evaluated code MUST return a widget with a view
+ * to be valid.
*
* @param args An object with area, options, cid, id, vpath & evaluate.
*/
@@ -65,42 +102,76 @@ export class ShellModel extends IpylabModel {
let widget: Widget | MainAreaWidget;
try {
- widget = await IpylabModel.toLuminoWidget(args);
+ widget = await ShellModel.toLuminoWidget(args);
// Create a new lumino widget
} catch (e) {
if (args.evaluate) {
// Evaluate code in python to get a panel and then add it to the shell.
- const jfem = await IpylabModel.JFEM.getModelByVpath(args.vpath);
+ const jfem = await ShellModel.JFEM.getModelByVpath(args.vpath);
return await jfem.scheduleOperation('shell_eval', args, 'object');
} else {
throw e;
}
}
+ args.cid =
+ args.cid || ShellModel.ConnectionModel.new_cid('ShellConnection');
if (args.asDocument && !(widget instanceof DocumentWidget)) {
- const jfem = await IpylabModel.JFEM.getModelByVpath(args.vpath);
+ const jfem = await ShellModel.JFEM.getModelByVpath(args.vpath);
const context = jfem.context as any;
widget.addClass('ipylab-Document');
const w = (widget = new DocumentWidget({ context, content: widget }));
w.node.removeChild(w.toolbar.node);
+ w.id = args.cid;
}
- args.cid =
- args.cid || IpylabModel.ConnectionModel.new_cid('ShellConnection');
- IpylabModel.ConnectionModel.registerConnection(args.cid, widget);
+ ShellModel.ConnectionModel.registerConnection(args.cid, widget);
widget.id = widget.id || args.cid || UUID.uuid4();
- IpylabModel.app.shell.add(widget as any, args.area || 'main', args.options);
+ ShellModel.app.shell.add(widget as any, args.area || 'main', args.options);
// Register widgets originating from IpyWidgets
if (args.ipy_model) {
- if (!IpylabModel.tracker.has(widget)) {
+ if (!ShellModel.tracker.has(widget)) {
(widget as any).ipylabSettings = args;
- IpylabModel.tracker.add(widget);
+ ShellModel.tracker.add(widget);
} else {
(widget as any).ipylabSettings.area = args.area;
(widget as any).ipylabSettings.options = args.options;
- IpylabModel.tracker.save(widget);
+ ShellModel.tracker.save(widget);
}
}
return widget;
}
+
+ /**
+ * Get the lumino widget from the shell using its id.
+ *
+ * @param id
+ */
+ static getLuminoWidgetFromShell(id: string): Widget | null {
+ for (const area of AREAS) {
+ for (const widget of ShellModel.labShell.widgets(area)) {
+ if (widget.id === id) {
+ return widget;
+ }
+ }
+ }
+ }
+
+ /**
+ * Get mapping of area to array widget ids for all areas.
+ */
+ static listWidgetIds(): any {
+ const data = Object.create(null);
+ for (const area of AREAS) {
+ const items: Array = [];
+ data[area] = items;
+ for (const widget of ShellModel.labShell.widgets(area)) {
+ items.push(widget.id);
+ }
+ }
+ return data;
+ }
+ readonly base: ILabShell;
}
+
+IpylabModel.ShellModel = ShellModel;