Skip to content

Commit 3f90116

Browse files
committed
feat: implement project save/load functionality from python with IPC communication
1 parent d0f900a commit 3f90116

4 files changed

Lines changed: 106 additions & 6 deletions

File tree

electron/main/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,24 @@ export function registerCommPushForwarding(
753753
subscribe(PDVMessageType.TREE_CHANGED, IPC.push.treeChanged, true);
754754
subscribe(PDVMessageType.PROJECT_LOADED, IPC.push.projectLoaded);
755755
subscribe(PDVMessageType.PROGRESS, IPC.push.progress);
756+
757+
// Kernel-initiated project operations — forward as menu actions so the
758+
// renderer drives the full save/load workflow (including code-cell
759+
// serialization and UI state updates).
760+
const forwardAsMenuAction = (
761+
type: string,
762+
action: "project:save" | "project:saveAs" | "project:openRecent",
763+
): void => {
764+
const handler = (msg: PDVMessage): void => {
765+
const payload = msg.payload as { save_dir?: string };
766+
win.webContents.send(IPC.push.menuAction, { action, path: payload.save_dir });
767+
};
768+
commRouter.onPush(type, handler);
769+
pushSubscriptions.push({ commRouter, type, handler });
770+
};
771+
forwardAsMenuAction(PDVMessageType.PROJECT_SAVE_REQUEST, "project:save");
772+
forwardAsMenuAction(PDVMessageType.PROJECT_SAVE_AS_REQUEST, "project:saveAs");
773+
forwardAsMenuAction(PDVMessageType.PROJECT_OPEN_REQUEST, "project:openRecent");
756774
}
757775

758776
/**

electron/main/pdv-protocol.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ export const PDVMessageType = {
7979
PROJECT_SAVE_RESPONSE: "pdv.project.save.response",
8080
/** Kernel → app (push). Progress update during save/load operations. */
8181
PROGRESS: "pdv.progress",
82+
/** Kernel → app (push). Kernel requests the app to save the current project. */
83+
PROJECT_SAVE_REQUEST: "pdv.project.save_request",
84+
/** Kernel → app (push). Kernel requests the app to save-as to a new directory. */
85+
PROJECT_SAVE_AS_REQUEST: "pdv.project.save_as_request",
86+
/** Kernel → app (push). Kernel requests the app to open a project from a directory. */
87+
PROJECT_OPEN_REQUEST: "pdv.project.open_request",
8288

8389
// Tree
8490
/** App → kernel. Request tree nodes at a given path. */

electron/renderer/src/app/useProjectWorkflow.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export function useProjectWorkflow(options: UseProjectWorkflowOptions) {
105105
}
106106
// If Save As is requested or no project is open yet, show the SaveAs dialog
107107
// instead of the native directory picker.
108-
if (options?.saveAs || (!options?.directory && !currentProjectDir)) {
108+
if (!options?.directory && (options?.saveAs || !currentProjectDir)) {
109109
setShowSaveAsDialog(true);
110110
// Returns false — not an error. The dialog will invoke handleSaveProject
111111
// again with { directory, projectName } once the user confirms.
@@ -226,11 +226,11 @@ export function useProjectWorkflow(options: UseProjectWorkflowOptions) {
226226
// project:open and project:openRecent are handled in App's menu listener
227227
// so they can route through openProjectFromWelcome when the kernel isn't ready.
228228
if (payload.action === 'project:save') {
229-
void handleSaveProject();
229+
void handleSaveProject(payload.path ? { directory: payload.path } : undefined);
230230
return;
231231
}
232232
if (payload.action === 'project:saveAs') {
233-
void handleSaveProject({ saveAs: true });
233+
void handleSaveProject({ saveAs: true, directory: payload.path });
234234
}
235235
});
236236
return () => unsubscribe();

pdv-python/pdv/namespace.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
namespace. Blocks reassignment of ``pdv_tree`` and ``pdv``.
88
99
- :class:`PDVApp`: the ``pdv`` object injected into the namespace.
10-
Exposes ``pdv.save()``, ``pdv.help()`` to users.
10+
Exposes ``pdv.save()``, ``pdv.save_project()``,
11+
``pdv.save_project_as()``, ``pdv.open_project()``,
12+
``pdv.help()`` to users.
1113
1214
- :func:`pdv_namespace`: returns a snapshot of the current kernel
1315
namespace for display in the Namespace panel, excluding PDV internals
@@ -127,16 +129,87 @@ def working_dir(self) -> Path:
127129
def save(self) -> None:
128130
"""Trigger a project save. Equivalent to File -> Save in the UI.
129131
130-
Sends a ``pdv.project.save`` comm message to the app. The app
132+
Sends a ``pdv.project.save_request`` push to the app. The app
131133
will prompt for a save location if no project is currently open.
132134
"""
133135
try:
134136
from pdv.comms import send_message # noqa: PLC0415
135137

136-
send_message("pdv.project.save", {})
138+
send_message("pdv.project.save_request", {})
137139
except RuntimeError:
138140
print("PDV: No comm channel open. Cannot trigger save.")
139141

142+
def save_project(self, path: str | None = None) -> None:
143+
"""Save the current project to a directory.
144+
145+
Sends a request to the app to perform a full project save
146+
(tree serialization, code cells, manifest). Equivalent to
147+
File -> Save with an explicit directory.
148+
149+
Parameters
150+
----------
151+
path : str or None
152+
Absolute or ``~``-prefixed path to the project directory.
153+
If None, saves to the current project location (equivalent
154+
to :meth:`save`).
155+
"""
156+
try:
157+
import os # noqa: PLC0415
158+
159+
from pdv.comms import send_message # noqa: PLC0415
160+
161+
if path is not None:
162+
resolved = os.path.realpath(os.path.expanduser(path))
163+
send_message("pdv.project.save_request", {"save_dir": resolved})
164+
else:
165+
send_message("pdv.project.save_request", {})
166+
except RuntimeError:
167+
print("PDV: No comm channel open. Cannot trigger save.")
168+
169+
def save_project_as(self, path: str) -> None:
170+
"""Save the project to a new directory (Save As).
171+
172+
Like :meth:`save_project`, but the given path becomes the new
173+
active project directory. Equivalent to File -> Save As with
174+
an explicit directory.
175+
176+
Parameters
177+
----------
178+
path : str
179+
Absolute or ``~``-prefixed path to the new project directory.
180+
"""
181+
try:
182+
import os # noqa: PLC0415
183+
184+
from pdv.comms import send_message # noqa: PLC0415
185+
186+
resolved = os.path.realpath(os.path.expanduser(path))
187+
send_message("pdv.project.save_as_request", {"save_dir": resolved})
188+
except RuntimeError:
189+
print("PDV: No comm channel open. Cannot trigger save.")
190+
191+
def open_project(self, path: str) -> None:
192+
"""Open a project from a directory.
193+
194+
Sends a request to the app to load the project at the given
195+
path. The current tree and code cells will be replaced with
196+
the contents of the loaded project.
197+
198+
Parameters
199+
----------
200+
path : str
201+
Absolute or ``~``-prefixed path to the project directory.
202+
"""
203+
try:
204+
import os # noqa: PLC0415
205+
206+
from pdv.comms import send_message # noqa: PLC0415
207+
208+
resolved = os.path.realpath(os.path.expanduser(path))
209+
send_message("pdv.project.open_request", {"save_dir": resolved})
210+
except RuntimeError:
211+
print("PDV: No comm channel open. Cannot open project.")
212+
140213
def add_file(self, source_path: str) -> "PDVFile":
141214
"""Import an arbitrary file into the tree as a :class:`PDVFile`.
142215
@@ -261,6 +334,9 @@ def help(self, topic: str | None = None) -> None:
261334
" pdv_tree.run_script('path') — run a script node\n"
262335
" pdv.working_dir — Path to the session working dir (for data files)\n"
263336
" pdv.save() — save the project\n"
337+
" pdv.save_project('path') — save project to a directory\n"
338+
" pdv.save_project_as('path') — save project to a new directory (Save As)\n"
339+
" pdv.open_project('path') — open a project from a directory\n"
264340
" pdv.add_file('path/to/file') — import a file into the tree\n"
265341
" pdv.new_note('path', title='My Note') — create a markdown note\n"
266342
" pdv.help('pdv_tree') — help on a specific topic\n"

0 commit comments

Comments
 (0)