1mod native_kernel;
2use std::{fmt::Debug, future::Future, path::PathBuf};
3
4use futures::{channel::mpsc, future::Shared};
5use gpui::{App, Entity, Task, Window};
6use language::LanguageName;
7use log;
8pub use native_kernel::*;
9
10mod remote_kernels;
11use project::{Project, ProjectPath, Toolchains, WorktreeId};
12use remote::RemoteConnectionOptions;
13pub use remote_kernels::*;
14
15mod ssh_kernel;
16pub use ssh_kernel::*;
17
18mod wsl_kernel;
19pub use wsl_kernel::*;
20
21use std::collections::HashMap;
22
23use anyhow::Result;
24use futures::{FutureExt, StreamExt};
25use gpui::{AppContext, AsyncWindowContext, Context};
26use jupyter_protocol::{JupyterKernelspec, JupyterMessageContent};
27use runtimelib::{
28 ClientControlConnection, ClientIoPubConnection, ClientShellConnection, ClientStdinConnection,
29 ExecutionState, JupyterMessage, KernelInfoReply,
30};
31use ui::{Icon, IconName, SharedString};
32use util::rel_path::RelPath;
33
34pub fn start_kernel_tasks<S: KernelSession + 'static>(
35 session: Entity<S>,
36 iopub_socket: ClientIoPubConnection,
37 shell_socket: ClientShellConnection,
38 control_socket: ClientControlConnection,
39 stdin_socket: ClientStdinConnection,
40 cx: &mut AsyncWindowContext,
41) -> (
42 futures::channel::mpsc::Sender<JupyterMessage>,
43 futures::channel::mpsc::Sender<JupyterMessage>,
44) {
45 let (mut shell_send, shell_recv) = shell_socket.split();
46 let (mut control_send, control_recv) = control_socket.split();
47 let (mut stdin_send, stdin_recv) = stdin_socket.split();
48
49 let (request_tx, mut request_rx) = futures::channel::mpsc::channel::<JupyterMessage>(100);
50 let (stdin_tx, mut stdin_rx) = futures::channel::mpsc::channel::<JupyterMessage>(100);
51
52 let recv_task = cx.spawn({
53 let session = session.clone();
54 let mut iopub = iopub_socket;
55 let mut shell = shell_recv;
56 let mut control = control_recv;
57 let mut stdin = stdin_recv;
58
59 async move |cx| -> anyhow::Result<()> {
60 loop {
61 let (channel, result) = futures::select! {
62 msg = iopub.read().fuse() => ("iopub", msg),
63 msg = shell.read().fuse() => ("shell", msg),
64 msg = control.read().fuse() => ("control", msg),
65 msg = stdin.read().fuse() => ("stdin", msg),
66 };
67 match result {
68 Ok(message) => {
69 session
70 .update_in(cx, |session, window, cx| {
71 session.route(&message, window, cx);
72 })
73 .ok();
74 }
75 Err(
76 ref err @ (runtimelib::RuntimeError::ParseError { .. }
77 | runtimelib::RuntimeError::SerdeError(_)),
78 ) => {
79 let error_detail = format!("Kernel issue on {channel} channel\n\n{err}");
80 log::warn!("kernel: {error_detail}");
81 session
82 .update_in(cx, |session, _window, cx| {
83 session.kernel_errored(error_detail, cx);
84 cx.notify();
85 })
86 .ok();
87 }
88 Err(err) => {
89 log::warn!("kernel: error reading from {channel}: {err:?}");
90 anyhow::bail!("{channel} recv: {err}");
91 }
92 }
93 }
94 }
95 });
96
97 let routing_task = cx.background_spawn(async move {
98 while let Some(message) = request_rx.next().await {
99 match message.content {
100 JupyterMessageContent::DebugRequest(_)
101 | JupyterMessageContent::InterruptRequest(_)
102 | JupyterMessageContent::ShutdownRequest(_) => {
103 control_send.send(message).await?;
104 }
105 _ => {
106 shell_send.send(message).await?;
107 }
108 }
109 }
110 anyhow::Ok(())
111 });
112
113 let stdin_routing_task = cx.background_spawn(async move {
114 while let Some(message) = stdin_rx.next().await {
115 stdin_send.send(message).await?;
116 }
117 anyhow::Ok(())
118 });
119
120 cx.spawn({
121 async move |cx| {
122 async fn with_name(
123 name: &'static str,
124 task: Task<Result<()>>,
125 ) -> (&'static str, Result<()>) {
126 (name, task.await)
127 }
128
129 let mut tasks = futures::stream::FuturesUnordered::new();
130 tasks.push(with_name("recv task", recv_task));
131 tasks.push(with_name("routing task", routing_task));
132 tasks.push(with_name("stdin routing task", stdin_routing_task));
133
134 while let Some((name, result)) = tasks.next().await {
135 if let Err(err) = result {
136 session.update(cx, |session, cx| {
137 session.kernel_errored(format!("handling failed for {name}: {err}"), cx);
138 cx.notify();
139 });
140 }
141 }
142 }
143 })
144 .detach();
145
146 (request_tx, stdin_tx)
147}
148
149pub trait KernelSession: Sized {
150 fn route(&mut self, message: &JupyterMessage, window: &mut Window, cx: &mut Context<Self>);
151 fn kernel_errored(&mut self, error_message: String, cx: &mut Context<Self>);
152}
153
154#[derive(Debug, Clone)]
155pub struct PythonEnvKernelSpecification {
156 pub name: String,
157 pub path: PathBuf,
158 pub kernelspec: JupyterKernelspec,
159 pub has_ipykernel: bool,
160 /// Display label for the environment type: "venv", "Conda", "Pyenv", etc.
161 pub environment_kind: Option<String>,
162}
163
164impl PartialEq for PythonEnvKernelSpecification {
165 fn eq(&self, other: &Self) -> bool {
166 self.name == other.name && self.path == other.path
167 }
168}
169
170impl Eq for PythonEnvKernelSpecification {}
171
172impl PythonEnvKernelSpecification {
173 pub fn as_local_spec(&self) -> LocalKernelSpecification {
174 LocalKernelSpecification {
175 name: self.name.clone(),
176 path: self.path.clone(),
177 kernelspec: self.kernelspec.clone(),
178 }
179 }
180
181 pub fn is_uv(&self) -> bool {
182 matches!(
183 self.environment_kind.as_deref(),
184 Some("uv" | "uv (Workspace)")
185 )
186 }
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub enum KernelSpecification {
191 JupyterServer(RemoteKernelSpecification),
192 Jupyter(LocalKernelSpecification),
193 PythonEnv(PythonEnvKernelSpecification),
194 SshRemote(SshRemoteKernelSpecification),
195 WslRemote(WslKernelSpecification),
196}
197
198#[derive(Debug, Clone)]
199pub struct SshRemoteKernelSpecification {
200 pub name: String,
201 pub path: SharedString,
202 pub kernelspec: JupyterKernelspec,
203}
204
205#[derive(Debug, Clone)]
206pub struct WslKernelSpecification {
207 pub name: String,
208 pub kernelspec: JupyterKernelspec,
209 pub distro: String,
210}
211
212impl PartialEq for SshRemoteKernelSpecification {
213 fn eq(&self, other: &Self) -> bool {
214 self.name == other.name
215 && self.kernelspec.argv == other.kernelspec.argv
216 && self.path == other.path
217 && self.kernelspec.display_name == other.kernelspec.display_name
218 && self.kernelspec.language == other.kernelspec.language
219 && self.kernelspec.interrupt_mode == other.kernelspec.interrupt_mode
220 && self.kernelspec.env == other.kernelspec.env
221 && self.kernelspec.metadata == other.kernelspec.metadata
222 }
223}
224
225impl Eq for SshRemoteKernelSpecification {}
226
227impl PartialEq for WslKernelSpecification {
228 fn eq(&self, other: &Self) -> bool {
229 self.name == other.name
230 && self.kernelspec.argv == other.kernelspec.argv
231 && self.kernelspec.display_name == other.kernelspec.display_name
232 && self.kernelspec.language == other.kernelspec.language
233 && self.kernelspec.interrupt_mode == other.kernelspec.interrupt_mode
234 && self.kernelspec.env == other.kernelspec.env
235 && self.kernelspec.metadata == other.kernelspec.metadata
236 && self.distro == other.distro
237 }
238}
239
240impl Eq for WslKernelSpecification {}
241
242impl KernelSpecification {
243 pub fn name(&self) -> SharedString {
244 match self {
245 Self::Jupyter(spec) => spec.name.clone().into(),
246 Self::PythonEnv(spec) => spec.name.clone().into(),
247 Self::JupyterServer(spec) => spec.name.clone().into(),
248 Self::SshRemote(spec) => spec.name.clone().into(),
249 Self::WslRemote(spec) => spec.kernelspec.display_name.clone().into(),
250 }
251 }
252
253 pub fn type_name(&self) -> SharedString {
254 match self {
255 Self::Jupyter(_) => "Jupyter".into(),
256 Self::PythonEnv(spec) => SharedString::from(
257 spec.environment_kind
258 .clone()
259 .unwrap_or_else(|| "Python Environment".to_string()),
260 ),
261 Self::JupyterServer(_) => "Jupyter Server".into(),
262 Self::SshRemote(_) => "SSH Remote".into(),
263 Self::WslRemote(_) => "WSL Remote".into(),
264 }
265 }
266
267 pub fn path(&self) -> SharedString {
268 SharedString::from(match self {
269 Self::Jupyter(spec) => spec.path.to_string_lossy().into_owned(),
270 Self::PythonEnv(spec) => spec.path.to_string_lossy().into_owned(),
271 Self::JupyterServer(spec) => spec.url.to_string(),
272 Self::SshRemote(spec) => spec.path.to_string(),
273 Self::WslRemote(spec) => spec.distro.clone(),
274 })
275 }
276
277 pub fn language(&self) -> SharedString {
278 SharedString::from(match self {
279 Self::Jupyter(spec) => spec.kernelspec.language.clone(),
280 Self::PythonEnv(spec) => spec.kernelspec.language.clone(),
281 Self::JupyterServer(spec) => spec.kernelspec.language.clone(),
282 Self::SshRemote(spec) => spec.kernelspec.language.clone(),
283 Self::WslRemote(spec) => spec.kernelspec.language.clone(),
284 })
285 }
286
287 pub fn has_ipykernel(&self) -> bool {
288 match self {
289 Self::Jupyter(_) | Self::JupyterServer(_) | Self::SshRemote(_) | Self::WslRemote(_) => {
290 true
291 }
292 Self::PythonEnv(spec) => spec.has_ipykernel,
293 }
294 }
295
296 pub fn environment_kind_label(&self) -> Option<SharedString> {
297 match self {
298 Self::PythonEnv(spec) => spec
299 .environment_kind
300 .as_ref()
301 .map(|kind| SharedString::from(kind.clone())),
302 Self::Jupyter(_) => Some("Jupyter".into()),
303 Self::JupyterServer(_) => Some("Jupyter Server".into()),
304 Self::SshRemote(_) => Some("SSH Remote".into()),
305 Self::WslRemote(_) => Some("WSL Remote".into()),
306 }
307 }
308
309 pub fn icon(&self, cx: &App) -> Icon {
310 let lang_name = match self {
311 Self::Jupyter(spec) => spec.kernelspec.language.clone(),
312 Self::PythonEnv(spec) => spec.kernelspec.language.clone(),
313 Self::JupyterServer(spec) => spec.kernelspec.language.clone(),
314 Self::SshRemote(spec) => spec.kernelspec.language.clone(),
315 Self::WslRemote(spec) => spec.kernelspec.language.clone(),
316 };
317
318 file_icons::FileIcons::get(cx)
319 .get_icon_for_type(&lang_name.to_lowercase(), cx)
320 .map(Icon::from_path)
321 .unwrap_or(Icon::new(IconName::ReplNeutral))
322 }
323}
324
325fn extract_environment_kind(toolchain_json: &serde_json::Value) -> Option<String> {
326 let kind_str = toolchain_json.get("kind")?.as_str()?;
327 let label = match kind_str {
328 "Conda" => "Conda",
329 "Pixi" => "pixi",
330 "Homebrew" => "Homebrew",
331 "Pyenv" => "global (Pyenv)",
332 "GlobalPaths" => "global",
333 "PyenvVirtualEnv" => "Pyenv",
334 "Pipenv" => "Pipenv",
335 "Poetry" => "Poetry",
336 "MacPythonOrg" => "global (Python.org)",
337 "MacCommandLineTools" => "global (Command Line Tools for Xcode)",
338 "LinuxGlobal" => "global",
339 "MacXCode" => "global (Xcode)",
340 "Venv" => "venv",
341 "VirtualEnv" => "virtualenv",
342 "VirtualEnvWrapper" => "virtualenvwrapper",
343 "WindowsStore" => "global (Windows Store)",
344 "WindowsRegistry" => "global (Windows Registry)",
345 "Uv" => "uv",
346 "UvWorkspace" => "uv (Workspace)",
347 _ => kind_str,
348 };
349 Some(label.to_string())
350}
351
352pub fn python_env_kernel_specifications(
353 project: &Entity<Project>,
354 worktree_id: WorktreeId,
355 cx: &mut App,
356) -> impl Future<Output = Result<Vec<KernelSpecification>>> + use<> {
357 let python_language = LanguageName::new_static("Python");
358 let is_remote = project.read(cx).is_remote();
359 let wsl_distro = project
360 .read(cx)
361 .remote_connection_options(cx)
362 .and_then(|opts| {
363 if let RemoteConnectionOptions::Wsl(wsl) = opts {
364 Some(wsl.distro_name)
365 } else {
366 None
367 }
368 });
369
370 let toolchains = project.read(cx).available_toolchains(
371 ProjectPath {
372 worktree_id,
373 path: RelPath::empty().into(),
374 },
375 python_language,
376 cx,
377 );
378 #[allow(unused)]
379 let worktree_root_path: Option<std::sync::Arc<std::path::Path>> = project
380 .read(cx)
381 .worktree_for_id(worktree_id, cx)
382 .map(|w| w.read(cx).abs_path());
383
384 let background_executor = cx.background_executor().clone();
385
386 async move {
387 let (toolchains, user_toolchains) = if let Some(Toolchains {
388 toolchains,
389 root_path: _,
390 user_toolchains,
391 }) = toolchains.await
392 {
393 (toolchains, user_toolchains)
394 } else {
395 return Ok(Vec::new());
396 };
397
398 let kernelspecs = user_toolchains
399 .into_values()
400 .flatten()
401 .chain(toolchains.toolchains)
402 .map(|toolchain| {
403 let wsl_distro = wsl_distro.clone();
404 background_executor.spawn(async move {
405 // For remote projects, we assume python is available assuming toolchain is reported.
406 // We can skip the `ipykernel` check or run it remotely.
407 // For MVP, lets trust the toolchain existence or do the check if it's cheap.
408 // `new_smol_command` runs locally. We need to run remotely if `is_remote`.
409
410 if is_remote {
411 let default_kernelspec = JupyterKernelspec {
412 argv: vec![
413 toolchain.path.to_string(),
414 "-m".to_string(),
415 "ipykernel_launcher".to_string(),
416 "-f".to_string(),
417 "{connection_file}".to_string(),
418 ],
419 display_name: toolchain.name.to_string(),
420 language: "python".to_string(),
421 interrupt_mode: None,
422 metadata: None,
423 env: None,
424 };
425
426 if let Some(distro) = wsl_distro {
427 log::debug!(
428 "python_env_kernel_specifications: returning WslRemote for toolchain {}",
429 toolchain.name
430 );
431 return Some(KernelSpecification::WslRemote(WslKernelSpecification {
432 name: toolchain.name.to_string(),
433 kernelspec: default_kernelspec,
434 distro,
435 }));
436 }
437
438 log::debug!(
439 "python_env_kernel_specifications: returning SshRemote for toolchain {}",
440 toolchain.name
441 );
442 return Some(KernelSpecification::SshRemote(
443 SshRemoteKernelSpecification {
444 name: format!("Remote {}", toolchain.name),
445 path: toolchain.path.clone(),
446 kernelspec: default_kernelspec,
447 },
448 ));
449 }
450
451 let python_path = toolchain.path.to_string();
452 let environment_kind = extract_environment_kind(&toolchain.as_json);
453
454 let has_ipykernel = util::command::new_command(&python_path)
455 .args(&["-c", "import ipykernel"])
456 .output()
457 .await
458 .map(|output| output.status.success())
459 .unwrap_or(false);
460
461 let mut env = HashMap::new();
462 if let Some(python_bin_dir) = PathBuf::from(&python_path).parent() {
463 if let Some(path_var) = std::env::var_os("PATH") {
464 let mut paths = std::env::split_paths(&path_var).collect::<Vec<_>>();
465 paths.insert(0, python_bin_dir.to_path_buf());
466 if let Ok(new_path) = std::env::join_paths(paths) {
467 env.insert("PATH".to_string(), new_path.to_string_lossy().to_string());
468 }
469 }
470
471 if let Some(venv_root) = python_bin_dir.parent() {
472 env.insert("VIRTUAL_ENV".to_string(), venv_root.to_string_lossy().to_string());
473 }
474 }
475
476 log::info!("Preparing Python kernel for toolchain: {}", toolchain.name);
477 log::info!("Python path: {}", python_path);
478 if let Some(path) = env.get("PATH") {
479 log::info!("Kernel PATH: {}", path);
480 } else {
481 log::info!("Kernel PATH not set in env");
482 }
483 if let Some(venv) = env.get("VIRTUAL_ENV") {
484 log::info!("Kernel VIRTUAL_ENV: {}", venv);
485 }
486
487 let kernelspec = JupyterKernelspec {
488 argv: vec![
489 python_path.clone(),
490 "-m".to_string(),
491 "ipykernel_launcher".to_string(),
492 "-f".to_string(),
493 "{connection_file}".to_string(),
494 ],
495 display_name: toolchain.name.to_string(),
496 language: "python".to_string(),
497 interrupt_mode: None,
498 metadata: None,
499 env: Some(env),
500 };
501
502 Some(KernelSpecification::PythonEnv(PythonEnvKernelSpecification {
503 name: toolchain.name.to_string(),
504 path: PathBuf::from(&python_path),
505 kernelspec,
506 has_ipykernel,
507 environment_kind,
508 }))
509 })
510 });
511
512 #[allow(unused_mut)]
513 let mut kernel_specs: Vec<KernelSpecification> = futures::stream::iter(kernelspecs)
514 .buffer_unordered(4)
515 .filter_map(|x| async move { x })
516 .collect::<Vec<_>>()
517 .await;
518
519 #[cfg(target_os = "windows")]
520 if kernel_specs.is_empty() && !is_remote {
521 if let Some(root_path) = worktree_root_path {
522 let root_path_str: std::borrow::Cow<str> = root_path.to_string_lossy();
523 let (distro, internal_path) =
524 if let Some(path_without_prefix) = root_path_str.strip_prefix(r"\\wsl$\") {
525 if let Some((distro, path)) = path_without_prefix.split_once('\\') {
526 let replaced_path: String = path.replace('\\', "/");
527 (Some(distro), Some(format!("/{}", replaced_path)))
528 } else {
529 (Some(path_without_prefix), Some("/".to_string()))
530 }
531 } else if let Some(path_without_prefix) =
532 root_path_str.strip_prefix(r"\\wsl.localhost\")
533 {
534 if let Some((distro, path)) = path_without_prefix.split_once('\\') {
535 let replaced_path: String = path.replace('\\', "/");
536 (Some(distro), Some(format!("/{}", replaced_path)))
537 } else {
538 (Some(path_without_prefix), Some("/".to_string()))
539 }
540 } else {
541 (None, None)
542 };
543
544 if let (Some(distro), Some(internal_path)) = (distro, internal_path) {
545 let python_path = format!("{}/.venv/bin/python", internal_path);
546 let check = util::command::new_command("wsl")
547 .args(&["-d", distro, "test", "-f", &python_path])
548 .output()
549 .await;
550
551 if check.is_ok() && check.unwrap().status.success() {
552 let default_kernelspec = JupyterKernelspec {
553 argv: vec![
554 python_path.clone(),
555 "-m".to_string(),
556 "ipykernel_launcher".to_string(),
557 "-f".to_string(),
558 "{connection_file}".to_string(),
559 ],
560 display_name: format!("WSL: {} (.venv)", distro),
561 language: "python".to_string(),
562 interrupt_mode: None,
563 metadata: None,
564 env: None,
565 };
566
567 kernel_specs.push(KernelSpecification::WslRemote(WslKernelSpecification {
568 name: format!("WSL: {} (.venv)", distro),
569 kernelspec: default_kernelspec,
570 distro: distro.to_string(),
571 }));
572 } else {
573 let check_system = util::command::new_command("wsl")
574 .args(&["-d", distro, "command", "-v", "python3"])
575 .output()
576 .await;
577
578 if check_system.is_ok() && check_system.unwrap().status.success() {
579 let default_kernelspec = JupyterKernelspec {
580 argv: vec![
581 "python3".to_string(),
582 "-m".to_string(),
583 "ipykernel_launcher".to_string(),
584 "-f".to_string(),
585 "{connection_file}".to_string(),
586 ],
587 display_name: format!("WSL: {} (System)", distro),
588 language: "python".to_string(),
589 interrupt_mode: None,
590 metadata: None,
591 env: None,
592 };
593
594 kernel_specs.push(KernelSpecification::WslRemote(
595 WslKernelSpecification {
596 name: format!("WSL: {} (System)", distro),
597 kernelspec: default_kernelspec,
598 distro: distro.to_string(),
599 },
600 ));
601 }
602 }
603 }
604 }
605 }
606
607 anyhow::Ok(kernel_specs)
608 }
609}
610
611pub trait RunningKernel: Send + Debug {
612 fn request_tx(&self) -> mpsc::Sender<JupyterMessage>;
613 fn stdin_tx(&self) -> mpsc::Sender<JupyterMessage>;
614 fn working_directory(&self) -> &PathBuf;
615 fn execution_state(&self) -> &ExecutionState;
616 fn set_execution_state(&mut self, state: ExecutionState);
617 fn kernel_info(&self) -> Option<&KernelInfoReply>;
618 fn set_kernel_info(&mut self, info: KernelInfoReply);
619 fn force_shutdown(&mut self, window: &mut Window, cx: &mut App) -> Task<anyhow::Result<()>>;
620 fn kill(&mut self);
621}
622
623#[derive(Debug, Clone)]
624pub enum KernelStatus {
625 Idle,
626 Busy,
627 Starting,
628 Error,
629 ShuttingDown,
630 Shutdown,
631 Restarting,
632}
633
634impl KernelStatus {
635 pub fn is_connected(&self) -> bool {
636 matches!(self, KernelStatus::Idle | KernelStatus::Busy)
637 }
638}
639
640impl ToString for KernelStatus {
641 fn to_string(&self) -> String {
642 match self {
643 KernelStatus::Idle => "Idle".to_string(),
644 KernelStatus::Busy => "Busy".to_string(),
645 KernelStatus::Starting => "Starting".to_string(),
646 KernelStatus::Error => "Error".to_string(),
647 KernelStatus::ShuttingDown => "Shutting Down".to_string(),
648 KernelStatus::Shutdown => "Shutdown".to_string(),
649 KernelStatus::Restarting => "Restarting".to_string(),
650 }
651 }
652}
653
654#[derive(Debug)]
655pub enum Kernel {
656 RunningKernel(Box<dyn RunningKernel>),
657 StartingKernel(Shared<Task<()>>),
658 ErroredLaunch(String),
659 ShuttingDown,
660 Shutdown,
661 Restarting,
662}
663
664impl From<&Kernel> for KernelStatus {
665 fn from(kernel: &Kernel) -> Self {
666 match kernel {
667 Kernel::RunningKernel(kernel) => match kernel.execution_state() {
668 ExecutionState::Idle => KernelStatus::Idle,
669 ExecutionState::Busy => KernelStatus::Busy,
670 ExecutionState::Unknown => KernelStatus::Error,
671 ExecutionState::Starting => KernelStatus::Starting,
672 ExecutionState::Restarting => KernelStatus::Restarting,
673 ExecutionState::Terminating => KernelStatus::ShuttingDown,
674 ExecutionState::AutoRestarting => KernelStatus::Restarting,
675 ExecutionState::Dead => KernelStatus::Error,
676 ExecutionState::Other(_) => KernelStatus::Error,
677 },
678 Kernel::StartingKernel(_) => KernelStatus::Starting,
679 Kernel::ErroredLaunch(_) => KernelStatus::Error,
680 Kernel::ShuttingDown => KernelStatus::ShuttingDown,
681 Kernel::Shutdown => KernelStatus::Shutdown,
682 Kernel::Restarting => KernelStatus::Restarting,
683 }
684 }
685}
686
687impl Kernel {
688 pub fn status(&self) -> KernelStatus {
689 self.into()
690 }
691
692 pub fn set_execution_state(&mut self, status: &ExecutionState) {
693 if let Kernel::RunningKernel(running_kernel) = self {
694 running_kernel.set_execution_state(status.clone());
695 }
696 }
697
698 pub fn set_kernel_info(&mut self, kernel_info: &KernelInfoReply) {
699 if let Kernel::RunningKernel(running_kernel) = self {
700 running_kernel.set_kernel_info(kernel_info.clone());
701 }
702 }
703
704 pub fn is_shutting_down(&self) -> bool {
705 match self {
706 Kernel::Restarting | Kernel::ShuttingDown => true,
707 Kernel::RunningKernel(_)
708 | Kernel::StartingKernel(_)
709 | Kernel::ErroredLaunch(_)
710 | Kernel::Shutdown => false,
711 }
712 }
713}