mod.rs

  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}