From 7d90559edf9fe27b1263c520dcd237c6c95aabd4 Mon Sep 17 00:00:00 2001 From: Matt Kafonek Date: Sat, 13 Jan 2024 20:08:59 -0500 Subject: [PATCH 1/9] add kernel-sidecar --- src-tauri/Cargo.lock | 212 +++++++++++++++++++++++++++++++++++++++---- src-tauri/Cargo.toml | 1 + 2 files changed, 193 insertions(+), 20 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index dd2f7e8..2a29972 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -62,6 +62,30 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + [[package]] name = "atk" version = "0.15.1" @@ -200,6 +224,9 @@ name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +dependencies = [ + "serde", +] [[package]] name = "cairo-rs" @@ -294,8 +321,10 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets 0.48.5", ] @@ -442,6 +471,16 @@ dependencies = [ "memoffset", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9bcf5bdbfdd6030fb4a1c497b5d5fc5921aa2f60d359a17e249c0e6df3de153" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.17" @@ -485,7 +524,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -495,7 +534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -519,7 +558,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -530,7 +569,20 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.41", + "syn 2.0.48", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] @@ -642,6 +694,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "env_logger" version = "0.10.1" @@ -765,6 +829,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -798,9 +863,15 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + [[package]] name = "futures-task" version = "0.3.29" @@ -814,8 +885,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1303,6 +1377,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + [[package]] name = "infer" version = "0.13.0" @@ -1408,6 +1488,28 @@ dependencies = [ "treediff", ] +[[package]] +name = "kernel-sidecar" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a006238b112b51539d964cd628a315364e32bfed0b257dca9baed83f583b546f" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "enum-as-inner", + "hex", + "indoc", + "lazy_static", + "rand 0.8.5", + "ring", + "serde", + "serde_json", + "tokio", + "uuid", + "zeromq", +] + [[package]] name = "kuchikiki" version = "0.8.2" @@ -1615,6 +1717,7 @@ name = "nteract-tauri" version = "0.0.0" dependencies = [ "env_logger", + "kernel-sidecar", "log", "serde", "serde_json", @@ -1918,7 +2021,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -2053,9 +2156,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -2071,9 +2174,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2229,6 +2332,20 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom 0.2.11", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2342,7 +2459,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -2364,7 +2481,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -2402,7 +2519,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -2531,6 +2648,12 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2591,9 +2714,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.41" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -2944,7 +3067,7 @@ checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -3028,7 +3151,21 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] @@ -3118,7 +3255,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -3211,6 +3348,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -3236,6 +3379,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ "getrandom 0.2.11", + "rand 0.8.5", + "serde", ] [[package]] @@ -3325,7 +3470,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -3347,7 +3492,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3854,3 +3999,30 @@ dependencies = [ "linux-raw-sys", "rustix", ] + +[[package]] +name = "zeromq" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2db35fbc7d9082d39a85c9831ec5dc7b7b135038d2f00bb5ff2a4c0275893da1" +dependencies = [ + "async-trait", + "asynchronous-codec", + "bytes", + "crossbeam-queue", + "dashmap", + "futures-channel", + "futures-io", + "futures-task", + "futures-util", + "log", + "num-traits", + "once_cell", + "parking_lot", + "rand 0.8.5", + "regex", + "thiserror", + "tokio", + "tokio-util", + "uuid", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ce030ed..b307824 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -20,6 +20,7 @@ serde_json = "1.0" ulid = "1.1.0" log = "0.4.20" env_logger = "0.10.1" +kernel-sidecar = "0.1.0" [features] # this feature is used for production builds or when `devPath` points to the filesystem From 4efb993bcb9672fe6a3a35b8f2ea6a628ec83f81 Mon Sep 17 00:00:00 2001 From: Matt Kafonek Date: Sat, 13 Jan 2024 20:09:23 -0500 Subject: [PATCH 2/9] add basic kernel sidecar integration, definitely needs refactoring --- src-tauri/src/main.rs | 134 ++++++++++++++++++------------------------ 1 file changed, 56 insertions(+), 78 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index c949a84..768b1a0 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,71 +4,96 @@ use tokio::sync::Mutex; use std::collections::HashMap; +use std::sync::Arc; use tauri::{Manager, State, Window}; -use ulid::Ulid; + // Structures for the notebook and cells -use log::{debug, info, warn}; +use log::{debug, info}; -use env_logger; -use tokio::task; +use kernel_sidecar::client::Client; + +use kernel_sidecar::handlers::{DebugHandler, Handler}; +use kernel_sidecar::kernels::JupyterKernel; +use kernel_sidecar::notebook::Notebook; -struct Notebook { - cells: HashMap, - cell_order: Vec, -} -// TODO: Implement cell types markdown and code -struct Cell { - id: String, - content: String, -} // AppState that holds the mapping from Window to Notebook struct AppState { + // Notebooks are just the document model, and could exist even if there's no underlying kernel notebooks: Mutex>, + // kernels are subprocesses started by the main tauri process, this is mostly here to keep + // a reference to them so they don't drop out of scope. + kernels: Mutex>, + // A kernel client is like jupyter_client in that it manages ZMQ connections to a Kernel. If + // a kernel is started outside this app, we could connect to it given a connection file, so + // kernel_clients map doesn't necessarily need to match 1:1 with kernels map + kernel_clients: Mutex>, } impl AppState { fn new() -> Self { Self { notebooks: Mutex::new(HashMap::new()), + kernels: Mutex::new(HashMap::new()), + kernel_clients: Mutex::new(HashMap::new()), } } async fn create_notebook(&self, window_id: &str) { let mut notebooks = self.notebooks.lock().await; - notebooks.insert( - window_id.to_string(), - Notebook { - cells: HashMap::new(), - cell_order: Vec::new(), - }, - ); + let nb = Notebook::new(); + notebooks.insert(window_id.to_string(), nb); + } + + async fn start_kernel(&self, window_id: &str) -> (JupyterKernel, Client) { + info!("Starting kernel for window with ID: {}", window_id); + let silent = true; // true = send ipykernel subprocess stdout to /dev/null + let kernel = JupyterKernel::ipython(silent); + let client = Client::new(kernel.connection_info.clone()).await; + (kernel, client) } // Perform the cell execution within the AppState context async fn execute_cell(&self, window_id: &str, cell_id: &str) -> bool { debug!( - "Attempting to execute cell with ID: {} in window with ID: {}", + "Executing cell with ID: {} in window with ID: {}", cell_id, window_id ); - let mut notebooks = self.notebooks.lock().await; + let mut kernel_clients = self.kernel_clients.lock().await; if let Some(notebook) = notebooks.get_mut(window_id) { - notebook.execute_cell(cell_id).await; - true - } else { - false + if let Some(cell) = notebook.get_cell(cell_id) { + // Start kernel if it doesn't exist + if !kernel_clients.contains_key(window_id) { + let (kernel, client) = self.start_kernel(window_id).await; + let mut kernels = self.kernels.lock().await; + kernels.insert(window_id.to_string(), kernel); + kernel_clients.insert(window_id.to_string(), client); + } + let kernel_client = kernel_clients.get(window_id).unwrap(); + + let source = cell.get_source(); + let debug_handler = Arc::new(Mutex::new(DebugHandler::new())); + let handlers: Vec>> = vec![debug_handler.clone()]; + + let action = kernel_client.execute_request(source, handlers); + action.await; + return true; + } } + false } + // Return cell_id async fn create_cell(&self, window_id: &str) -> Option { debug!("Creating a new cell in window with ID: {}", window_id); let mut notebooks = self.notebooks.lock().await; if let Some(notebook) = notebooks.get_mut(window_id) { - Some(notebook.create_cell()) + let new_cell = notebook.add_code_cell(""); + Some(new_cell.id().to_string()) } else { None } @@ -83,7 +108,9 @@ impl AppState { let mut notebooks = self.notebooks.lock().await; if let Some(notebook) = notebooks.get_mut(window_id) { - notebook.update_cell(cell_id, new_content); + if let Some(cell) = notebook.get_mut_cell(cell_id) { + cell.set_source(new_content); + } true } else { false @@ -91,57 +118,8 @@ impl AppState { } } -impl Notebook { - async fn execute_cell(&mut self, cell_id: &str) { - if let Some(cell) = self.cells.get(cell_id) { - let cell_content = cell.content.clone(); - let result = task::spawn_blocking(move || { - println!("Executing cell with content: {}", cell_content); - "Pretend that execution got queued" - }) - .await; - - match result { - Ok(_) => { - println!("Cell execution queued"); - } - Err(_) => { - println!("Cell failed to queue"); - } - } - } else { - warn!("Cell with ID: {} not found", cell_id); - } - } - - fn create_cell(&mut self) -> String { - let cell_id = Ulid::new().to_string(); - let new_cell = Cell { - id: cell_id.clone(), - content: String::new(), - }; - - self.cells.insert(cell_id.clone(), new_cell); - self.cell_order.push(cell_id.clone()); - - debug!("Created cell with ID: {}", cell_id.clone()); - - cell_id - } - - // Method to update an existing cell in the notebook - fn update_cell(&mut self, cell_id: &str, new_content: &str) { - if let Some(cell) = self.cells.get_mut(cell_id) { - cell.content = new_content.to_string(); - } - } -} - #[tauri::command] -async fn create_cell( - state: State<'_, AppState>, - window: Window, -) -> Result, String> { +async fn create_cell(state: State<'_, AppState>, window: Window) -> Result, String> { let window_id = window.label(); // Use the window label as a unique identifier Ok(state.create_cell(window_id).await) } From 8b57149ffd4497285226ef698b9c5e133c5f508b Mon Sep 17 00:00:00 2001 From: Matt Kafonek Date: Sun, 14 Jan 2024 16:42:31 -0500 Subject: [PATCH 3/9] hacking with Kyle, emit outputs to frontend in really naive serialization --- src-tauri/src/main.rs | 21 ++++++++++++--------- src/main.tsx | 6 ++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 768b1a0..ffb3671 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -10,15 +10,12 @@ use tauri::{Manager, State, Window}; // Structures for the notebook and cells use log::{debug, info}; - -use kernel_sidecar::client::Client; +use kernel_sidecar::{client::Client, handlers::SimpleOutputHandler}; use kernel_sidecar::handlers::{DebugHandler, Handler}; use kernel_sidecar::kernels::JupyterKernel; use kernel_sidecar::notebook::Notebook; - - // AppState that holds the mapping from Window to Notebook struct AppState { // Notebooks are just the document model, and could exist even if there's no underlying kernel @@ -56,7 +53,8 @@ impl AppState { } // Perform the cell execution within the AppState context - async fn execute_cell(&self, window_id: &str, cell_id: &str) -> bool { + async fn execute_cell(&self, window: Window, cell_id: &str) -> bool { + let window_id = window.label(); // Use the window label as a unique identifier debug!( "Executing cell with ID: {} in window with ID: {}", cell_id, window_id @@ -76,10 +74,16 @@ impl AppState { let source = cell.get_source(); let debug_handler = Arc::new(Mutex::new(DebugHandler::new())); - let handlers: Vec>> = vec![debug_handler.clone()]; + let output_handler = Arc::new(Mutex::new(SimpleOutputHandler::new())); + let handlers: Vec>> = + vec![debug_handler.clone(), output_handler.clone()]; - let action = kernel_client.execute_request(source, handlers); + let action = kernel_client.execute_request(source, handlers).await; action.await; + + let finished_output = &output_handler.lock().await.output; + + window.emit("output", finished_output.clone()).unwrap(); return true; } } @@ -130,8 +134,7 @@ async fn execute_cell( window: Window, cell_id: &str, ) -> Result { - let window_id = window.label(); // Use the window label as a unique identifier - Ok(state.execute_cell(window_id, cell_id).await) + Ok(state.execute_cell(window, cell_id).await) } #[tauri::command] diff --git a/src/main.tsx b/src/main.tsx index e83bf95..374b3b8 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,7 +2,13 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; +import { emit, listen } from '@tauri-apps/api/event' +// listen to the `click` event and get a function to remove the event listener +// there's also a `once` function that subscribes to an event and automatically unsubscribes the listener on the first event +const unlisten = await listen('output', (event) => { + console.log(event) +}) ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( From b3f50f0334fd457f8054a53a16a3415ad7f20eb2 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sun, 14 Jan 2024 16:42:50 -0800 Subject: [PATCH 4/9] set up an event listener for outputs within useCell --- src-tauri/src/main.rs | 5 ++- src/components/Cell.tsx | 34 +++++++++------ src/hooks/useCell.ts | 96 ++++++++++++++++++++++++++++------------- src/main.tsx | 6 --- 4 files changed, 92 insertions(+), 49 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index ffb3671..21e1e0b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -83,7 +83,10 @@ impl AppState { let finished_output = &output_handler.lock().await.output; - window.emit("output", finished_output.clone()).unwrap(); + window.emit( + format!("cell-outputs-{}", cell_id).as_str(), + Some(finished_output.clone()), + ).unwrap(); return true; } } diff --git a/src/components/Cell.tsx b/src/components/Cell.tsx index 649eaac..ad538bc 100644 --- a/src/components/Cell.tsx +++ b/src/components/Cell.tsx @@ -4,12 +4,13 @@ import { useCell } from "@/hooks/useCell"; import { Editor } from "@/components//Editor"; const Cell = ({ cellId }: { cellId: string }) => { - const { executeCell, cellState, executionCount } = useCell(cellId); + const { executeCell, executionState, executionCount, outputs } = + useCell(cellId); let actionIcon = "▶"; let showExecutionCountAs = executionCount === null ? " " : executionCount; - switch (cellState) { + switch (executionState) { case "idle": // Let them try again case "errored": @@ -24,16 +25,25 @@ const Cell = ({ cellId }: { cellId: string }) => { } return ( -
- - +
+
+ + +
+ {outputs && outputs.length > 0 ? ( +
{JSON.stringify(outputs, null, 2)}
+ ) : null}
); }; diff --git a/src/hooks/useCell.ts b/src/hooks/useCell.ts index 396e00a..5cc678a 100644 --- a/src/hooks/useCell.ts +++ b/src/hooks/useCell.ts @@ -1,46 +1,82 @@ -import { useState, useCallback } from "react"; +import { useState, useEffect, useCallback, useReducer } from "react"; import { invoke } from "@tauri-apps/api/tauri"; +import { listen } from '@tauri-apps/api/event' -type CellStates = "idle" | "queued" | "busy" | "errored" | "submitted"; - -export function useCell(cellId: string) { - const [content, setContent] = useState(""); - - // We also find out the execution count, whether the cell is queued, busy, or - // errored, and the output of the cell. We can use this to display the - // execution count, a spinner, or an error message. - - // Execution count is only set by the backend. - - const executionCount = null; // TODO: get from backend +enum ExecutionState { + Idle = "idle", + Queued = "queued", + Busy = "busy", + Errored = "errored", + Submitted = "submitted" +} - // Queued, busy, and errored are set by the backend. However, we have to - // have our own state for queued for before the backend has acknowledged the - // execution request. +type Action = { type: 'setSubmitted' } | { type: 'reset' } | {type: "setOutputs", outputs: []} - const [executionSubmitted, setExecutionSubmitted] = useState(false); +type CellState = { + executionState: ExecutionState; + outputs: []; +} - let cellState: CellStates = "idle"; +const initialState: CellState = { + executionState: ExecutionState.Idle, + outputs: [] +}; - if (executionSubmitted) { - cellState = "submitted"; +function cellReducer(state: CellState, action: Action) { + switch (action.type) { + case 'setSubmitted': + return { ...state, executionState: ExecutionState.Submitted }; + case 'reset': + return { ...state, executionState: ExecutionState.Idle }; + case 'setOutputs': + return {...state, outputs: action.outputs } + default: + return state; } +} +export function useCell(cellId: string) { + const [content, setContent] = useState(""); + const [state, dispatch] = useReducer(cellReducer, initialState); + const executeCell = useCallback(async () => { - setExecutionSubmitted(true); - await invoke("execute_cell", { cellId }); - setExecutionSubmitted(false); + dispatch({ type: 'setSubmitted' }); + try { + await invoke("execute_cell", { cellId }); + } catch (error) { + console.error(error); + // Handle error + } + dispatch({ type: 'reset' }); }, [cellId]); - const updateCell = useCallback( - async (newContent: string) => { + const updateCell = useCallback(async (newContent: string) => { + try { await invoke("update_cell", { cellId, newContent }); setContent(newContent); - }, - [cellId] - ); + } catch (error) { + console.error(error); + // Handle error + } + }, [cellId]); + + useEffect(() => { + const setupListener = async () => { + const unlisten = await listen(`cell-outputs-${cellId}`, (event) => { + // Q(Kyle): Do we need to check if the event.windowLabel matches ours + const outputs = event.payload as []; - // TODO(kyle): Listen for events from the backend to update the cell content + dispatch({type: "setOutputs", outputs}) + + }); + + return () => { + unlisten(); + }; + }; + + setupListener(); + }, [cellId]); - return { content, executeCell, updateCell, cellState: cellState as CellStates, executionCount }; + return { ...state, content, executeCell, updateCell, executionCount: null}; } diff --git a/src/main.tsx b/src/main.tsx index 374b3b8..e83bf95 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,13 +2,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; -import { emit, listen } from '@tauri-apps/api/event' -// listen to the `click` event and get a function to remove the event listener -// there's also a `once` function that subscribes to an event and automatically unsubscribes the listener on the first event -const unlisten = await listen('output', (event) => { - console.log(event) -}) ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( From 8c4157c0f0e6b5e3b225c3906640d59669ea1c8f Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sun, 14 Jan 2024 18:09:11 -0800 Subject: [PATCH 5/9] try to pin down versions for builds --- src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2a29972..804d0cc 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1725,6 +1725,7 @@ dependencies = [ "tauri-build", "tokio", "ulid", + "zeromq", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b307824..78e6807 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -15,7 +15,8 @@ tauri-build = { version = "1.5", features = [] } [dependencies] tauri = { version = "1.5", features = ["shell-open"] } serde = { version = "1.0", features = ["derive"] } -tokio = { version = "1.35.1", features = ["full"] } +tokio = { version = "1.35.1", features = [ "rt", "rt-multi-thread", "sync", "fs", "io-util" ] } +zeromq = { version = "0.3.4", features = ["tcp-transport", "tokio-runtime"] } serde_json = "1.0" ulid = "1.1.0" log = "0.4.20" From 6b33d3e0893e721d17eefffa1440aa507d7e5146 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sun, 14 Jan 2024 18:20:17 -0800 Subject: [PATCH 6/9] attempt to pin down *nix vs windows for transport --- src-tauri/Cargo.toml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 78e6807..523a6e3 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -23,6 +23,17 @@ log = "0.4.20" env_logger = "0.10.1" kernel-sidecar = "0.1.0" +[target.'cfg(windows)'.dependencies.zeromq] +version = "=0.3.4" +default-features = false +features = ["tcp-transport", "tokio-runtime"] + + +[target.'cfg(unix)'.dependencies.zeromq] +version = "=0.3.4" +default-features = false +features = ["ipc-transport", "tcp-transport", "tokio-runtime"] + [features] # this feature is used for production builds or when `devPath` points to the filesystem # DO NOT REMOVE!! From 1c2df1912f9eda8b281e7486225295cb60013304 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sun, 14 Jan 2024 20:16:51 -0800 Subject: [PATCH 7/9] turn off default features of zeromq --- src-tauri/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 523a6e3..3911f7a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -16,7 +16,7 @@ tauri-build = { version = "1.5", features = [] } tauri = { version = "1.5", features = ["shell-open"] } serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.35.1", features = [ "rt", "rt-multi-thread", "sync", "fs", "io-util" ] } -zeromq = { version = "0.3.4", features = ["tcp-transport", "tokio-runtime"] } +zeromq = { version = "0.3.4", features = ["tcp-transport", "tokio-runtime"], default-features = false } serde_json = "1.0" ulid = "1.1.0" log = "0.4.20" From 48431858a5adb6bc3111a7f39641f84cdd0e814d Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Mon, 15 Jan 2024 06:57:44 -0800 Subject: [PATCH 8/9] bump zeromq --- src-tauri/Cargo.lock | 4 ++-- src-tauri/Cargo.toml | 13 +------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 804d0cc..1c8ccaf 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4003,9 +4003,9 @@ dependencies = [ [[package]] name = "zeromq" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2db35fbc7d9082d39a85c9831ec5dc7b7b135038d2f00bb5ff2a4c0275893da1" +checksum = "9ad3ffd65d6ae06a9eece312a64c3dfa2151a70a5c99051e2080828653cbda45" dependencies = [ "async-trait", "asynchronous-codec", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3911f7a..1e00800 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -16,24 +16,13 @@ tauri-build = { version = "1.5", features = [] } tauri = { version = "1.5", features = ["shell-open"] } serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.35.1", features = [ "rt", "rt-multi-thread", "sync", "fs", "io-util" ] } -zeromq = { version = "0.3.4", features = ["tcp-transport", "tokio-runtime"], default-features = false } +zeromq = { version = "0.3.5", features = ["tcp-transport", "tokio-runtime"], default-features = false } serde_json = "1.0" ulid = "1.1.0" log = "0.4.20" env_logger = "0.10.1" kernel-sidecar = "0.1.0" -[target.'cfg(windows)'.dependencies.zeromq] -version = "=0.3.4" -default-features = false -features = ["tcp-transport", "tokio-runtime"] - - -[target.'cfg(unix)'.dependencies.zeromq] -version = "=0.3.4" -default-features = false -features = ["ipc-transport", "tcp-transport", "tokio-runtime"] - [features] # this feature is used for production builds or when `devPath` points to the filesystem # DO NOT REMOVE!! From 0a45425b5f5cad585165475e54c9232fe4ed05d2 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Mon, 15 Jan 2024 07:10:14 -0800 Subject: [PATCH 9/9] bring tokio back to fully featured --- src-tauri/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1e00800..a367cc1 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -15,7 +15,7 @@ tauri-build = { version = "1.5", features = [] } [dependencies] tauri = { version = "1.5", features = ["shell-open"] } serde = { version = "1.0", features = ["derive"] } -tokio = { version = "1.35.1", features = [ "rt", "rt-multi-thread", "sync", "fs", "io-util" ] } +tokio = { version = "1.35.1", features = ["full"] } zeromq = { version = "0.3.5", features = ["tcp-transport", "tokio-runtime"], default-features = false } serde_json = "1.0" ulid = "1.1.0"