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