Create `RunningKernel` trait to allow for native and remote jupyter kernels (#20842)

Kyle Kelley created

Starts setting up a `RunningKernel` trait to make the remote kernel
implementation easy to get started with. No release notes until this is
all hooked up.

Release Notes:

- N/A

Change summary

Cargo.lock                                | 345 +++++++++++-------------
Cargo.toml                                |   6 
crates/quick_action_bar/src/repl_menu.rs  |   2 
crates/repl/Cargo.toml                    |   2 
crates/repl/src/kernels/mod.rs            | 227 ++++++++++++++++
crates/repl/src/kernels/native_kernel.rs  | 304 +++------------------
crates/repl/src/kernels/remote_kernels.rs | 122 ++++++++
crates/repl/src/repl.rs                   |   2 
crates/repl/src/session.rs                |  38 +-
9 files changed, 599 insertions(+), 449 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -10,7 +10,7 @@ dependencies = [
  "auto_update",
  "editor",
  "extension_host",
- "futures 0.3.30",
+ "futures 0.3.31",
  "gpui",
  "language",
  "lsp",
@@ -23,19 +23,13 @@ dependencies = [
 
 [[package]]
 name = "addr2line"
-version = "0.24.1"
+version = "0.24.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
 dependencies = [
- "gimli 0.31.0",
+ "gimli 0.31.1",
 ]
 
-[[package]]
-name = "adler"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
-
 [[package]]
 name = "adler2"
 version = "2.0.0"
@@ -100,8 +94,8 @@ dependencies = [
  "miow",
  "parking_lot",
  "piper",
- "polling 3.7.3",
- "regex-automata 0.4.7",
+ "polling 3.7.4",
+ "regex-automata 0.4.9",
  "rustix-openpty",
  "serde",
  "signal-hook",
@@ -124,9 +118,9 @@ checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
 
 [[package]]
 name = "allocator-api2"
-version = "0.2.18"
+version = "0.2.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
+checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"
 
 [[package]]
 name = "alsa"
@@ -192,9 +186,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "anstream"
-version = "0.6.15"
+version = "0.6.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -207,36 +201,36 @@ dependencies = [
 
 [[package]]
 name = "anstyle"
-version = "1.0.8"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
 
 [[package]]
 name = "anstyle-parse"
-version = "0.2.5"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
 dependencies = [
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle-query"
-version = "1.1.1"
+version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
 dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.4"
+version = "3.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
+checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
 dependencies = [
  "anstyle",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -245,13 +239,13 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "chrono",
- "futures 0.3.30",
+ "futures 0.3.31",
  "http_client",
  "schemars",
  "serde",
  "serde_json",
  "strum 0.25.0",
- "thiserror",
+ "thiserror 1.0.69",
  "util",
 ]
 
@@ -278,9 +272,9 @@ dependencies = [
 
 [[package]]
 name = "arbitrary"
-version = "1.3.2"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
+checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
 
 [[package]]
 name = "arg_enum_proc_macro"
@@ -301,9 +295,9 @@ checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
 
 [[package]]
 name = "arrayref"
-version = "0.3.8"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
 
 [[package]]
 name = "arrayvec"
@@ -396,7 +390,7 @@ dependencies = [
  "env_logger 0.11.5",
  "feature_flags",
  "fs",
- "futures 0.3.30",
+ "futures 0.3.31",
  "fuzzy",
  "globset",
  "gpui",
@@ -463,7 +457,7 @@ dependencies = [
  "collections",
  "derive_more",
  "extension",
- "futures 0.3.30",
+ "futures 0.3.31",
  "gpui",
  "language",
  "language_model",
@@ -573,14 +567,14 @@ dependencies = [
 
 [[package]]
 name = "async-executor"
-version = "1.13.0"
+version = "1.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7"
+checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec"
 dependencies = [
  "async-task",
  "concurrent-queue",
- "fastrand 2.1.1",
- "futures-lite 2.3.0",
+ "fastrand 2.2.0",
+ "futures-lite 2.5.0",
  "slab",
 ]
 
@@ -604,7 +598,7 @@ checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
 dependencies = [
  "async-lock 3.4.0",
  "blocking",
- "futures-lite 2.3.0",
+ "futures-lite 2.5.0",
 ]
 
 [[package]]
@@ -615,10 +609,10 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
 dependencies = [
  "async-channel 2.3.1",
  "async-executor",
- "async-io 2.3.4",
+ "async-io 2.4.0",
  "async-lock 3.4.0",
  "blocking",
- "futures-lite 2.3.0",
+ "futures-lite 2.5.0",
  "once_cell",
 ]
 
@@ -644,18 +638,18 @@ dependencies = [
 
 [[package]]
 name = "async-io"
-version = "2.3.4"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8"
+checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059"
 dependencies = [
  "async-lock 3.4.0",
  "cfg-if",
  "concurrent-queue",
  "futures-io",
- "futures-lite 2.3.0",
+ "futures-lite 2.5.0",
  "parking",
- "polling 3.7.3",
- "rustix 0.38.35",
+ "polling 3.7.4",
+ "rustix 0.38.40",
  "slab",
  "tracing",
  "windows-sys 0.59.0",
@@ -689,7 +683,7 @@ checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec"
 dependencies = [
  "futures-util",
  "native-tls",
- "thiserror",
+ "thiserror 1.0.69",
  "url",
 ]
 
@@ -710,9 +704,9 @@ version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
 dependencies = [
- "async-io 2.3.4",
+ "async-io 2.4.0",
  "blocking",
- "futures-lite 2.3.0",
+ "futures-lite 2.5.0",
 ]
 
 [[package]]
@@ -720,7 +714,7 @@ name = "async-pipe"
 version = "0.1.3"
 source = "git+https://github.com/zed-industries/async-pipe-rs?rev=82d00a04211cf4e1236029aa03e6b6ce2a74c553#82d00a04211cf4e1236029aa03e6b6ce2a74c553"
 dependencies = [
- "futures 0.3.30",
+ "futures 0.3.31",
  "log",
 ]
 
@@ -737,28 +731,27 @@ dependencies = [
  "cfg-if",
  "event-listener 3.1.0",
  "futures-lite 1.13.0",
- "rustix 0.38.35",
+ "rustix 0.38.40",
  "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "async-process"
-version = "2.2.4"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374"
+checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
 dependencies = [
  "async-channel 2.3.1",
- "async-io 2.3.4",
+ "async-io 2.4.0",
  "async-lock 3.4.0",
  "async-signal",
  "async-task",
  "blocking",
  "cfg-if",
  "event-listener 5.3.1",
- "futures-lite 2.3.0",
- "rustix 0.38.35",
+ "futures-lite 2.5.0",
+ "rustix 0.38.40",
  "tracing",
- "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -789,13 +782,13 @@ version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
 dependencies = [
- "async-io 2.3.4",
+ "async-io 2.4.0",
  "async-lock 3.4.0",
  "atomic-waker",
  "cfg-if",
  "futures-core",
  "futures-io",
- "rustix 0.38.35",
+ "rustix 0.38.40",
  "signal-hook-registry",
  "slab",
  "windows-sys 0.59.0",
@@ -803,21 +796,21 @@ dependencies = [
 
 [[package]]
 name = "async-std"
-version = "1.12.0"
+version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
+checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615"
 dependencies = [
  "async-attributes",
  "async-channel 1.9.0",
  "async-global-executor",
- "async-io 1.13.0",
- "async-lock 2.8.0",
- "async-process 1.8.1",
+ "async-io 2.4.0",
+ "async-lock 3.4.0",
+ "async-process 2.3.0",
  "crossbeam-utils",
  "futures-channel",
  "futures-core",
  "futures-io",
- "futures-lite 1.13.0",
+ "futures-lite 2.5.0",
  "gloo-timers",
  "kv-log-macro",
  "log",
@@ -831,9 +824,9 @@ dependencies = [
 
 [[package]]
 name = "async-stream"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
+checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
 dependencies = [
  "async-stream-impl",
  "futures-core",
@@ -842,9 +835,9 @@ dependencies = [
 
 [[package]]
 name = "async-stream-impl"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
+checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -867,7 +860,7 @@ dependencies = [
  "serde_qs 0.10.1",
  "smart-default",
  "smol_str",
- "thiserror",
+ "thiserror 1.0.69",
  "tokio",
 ]
 
@@ -915,6 +908,20 @@ dependencies = [
  "syn 2.0.87",
 ]
 
+[[package]]
+name = "async-tungstenite"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce01ac37fdc85f10a43c43bc582cbd566720357011578a935761075f898baf58"
+dependencies = [
+ "async-std",
+ "futures-io",
+ "futures-util",
+ "log",
+ "pin-project-lite",
+ "tungstenite 0.19.0",
+]
+
 [[package]]
 name = "async-tungstenite"
 version = "0.28.0"
@@ -947,9 +954,9 @@ checksum = "00b9f7252833d5ed4b00aa9604b563529dd5e11de9c23615de2dcdf91eb87b52"
 dependencies = [
  "async-compression",
  "crc32fast",
- "futures-lite 2.3.0",
+ "futures-lite 2.5.0",
  "pin-project",
- "thiserror",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
@@ -958,7 +965,7 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233"
 dependencies = [
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "futures-sink",
  "futures-util",
  "memchr",
@@ -1028,9 +1035,9 @@ dependencies = [
 
 [[package]]
 name = "autocfg"
-version = "1.3.0"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
 
 [[package]]
 name = "av1-grain"
@@ -1048,18 +1055,18 @@ dependencies = [
 
 [[package]]
 name = "avif-serialize"
-version = "0.8.1"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2"
+checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62"
 dependencies = [
  "arrayvec",
 ]
 
 [[package]]
 name = "aws-config"
-version = "1.5.5"
+version = "1.5.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e95816a168520d72c0e7680c405a5a8c1fb6a035b4bc4b9d7b0de8e1a941697"
+checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924"
 dependencies = [
  "aws-credential-types",
  "aws-runtime",
@@ -1073,8 +1080,8 @@ dependencies = [
  "aws-smithy-runtime-api",
  "aws-smithy-types",
  "aws-types",
- "bytes 1.7.2",
- "fastrand 2.1.1",
+ "bytes 1.8.0",
+ "fastrand 2.2.0",
  "hex",
  "http 0.2.12",
  "ring",
@@ -1112,8 +1119,8 @@ dependencies = [
  "aws-smithy-runtime-api",
  "aws-smithy-types",
  "aws-types",
- "bytes 1.7.2",
- "fastrand 2.1.1",
+ "bytes 1.8.0",
+ "fastrand 2.2.0",
  "http 0.2.12",
  "http-body 0.4.6",
  "once_cell",
@@ -1138,7 +1145,7 @@ dependencies = [
  "aws-smithy-runtime-api",
  "aws-smithy-types",
  "aws-types",
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "http 0.2.12",
  "once_cell",
  "regex-lite",
@@ -1147,11 +1154,10 @@ dependencies = [
 
 [[package]]
 name = "aws-sdk-s3"
-version = "1.47.0"
+version = "1.61.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cca49303c05d2a740b8a4552fac63a4db6ead84f7e7eeed04761fd3014c26f25"
+checksum = "0e531658a0397d22365dfe26c3e1c0c8448bf6a3a2d8a098ded802f2b1261615"
 dependencies = [
- "ahash 0.8.11",
  "aws-credential-types",
  "aws-runtime",
  "aws-sigv4",
@@ -1165,8 +1171,8 @@ dependencies = [
  "aws-smithy-types",
  "aws-smithy-xml",
  "aws-types",
- "bytes 1.7.2",
- "fastrand 2.1.1",
+ "bytes 1.8.0",
+ "fastrand 2.2.0",
  "hex",
  "hmac",
  "http 0.2.12",
@@ -1182,9 +1188,9 @@ dependencies = [
 
 [[package]]
 name = "aws-sdk-sso"
-version = "1.40.0"
+version = "1.49.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5879bec6e74b648ce12f6085e7245417bc5f6d672781028384d2e494be3eb6d"
+checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4"
 dependencies = [
  "aws-credential-types",
  "aws-runtime",
@@ -1195,7 +1201,7 @@ dependencies = [
  "aws-smithy-runtime-api",
  "aws-smithy-types",
  "aws-types",
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "http 0.2.12",
  "once_cell",
  "regex-lite",
@@ -1204,9 +1210,9 @@ dependencies = [
 
 [[package]]
 name = "aws-sdk-ssooidc"
-version = "1.41.0"
+version = "1.50.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ef4cd9362f638c22a3b959fd8df292e7e47fdf170270f86246b97109b5f2f7d"
+checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee"
 dependencies = [
  "aws-credential-types",
  "aws-runtime",
@@ -1217,7 +1223,7 @@ dependencies = [
  "aws-smithy-runtime-api",
  "aws-smithy-types",
  "aws-types",
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "http 0.2.12",
  "once_cell",
  "regex-lite",
@@ -1226,9 +1232,9 @@ dependencies = [
 
 [[package]]
 name = "aws-sdk-sts"
-version = "1.40.0"
+version = "1.50.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b1e2735d2ab28b35ecbb5496c9d41857f52a0d6a0075bbf6a8af306045ea6f6"
+checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5"
 dependencies = [
  "aws-credential-types",
  "aws-runtime",
@@ -1258,7 +1264,7 @@ dependencies = [
  "aws-smithy-http",
  "aws-smithy-runtime-api",
  "aws-smithy-types",
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "crypto-bigint 0.5.5",
  "form_urlencoded",
  "hex",
@@ -1289,13 +1295,13 @@ dependencies = [
 
 [[package]]
 name = "aws-smithy-checksums"
-version = "0.60.12"
+version = "0.60.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "598b1689d001c4d4dc3cb386adb07d37786783aee3ac4b324bcadac116bf3d23"
+checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795"
 dependencies = [
  "aws-smithy-http",
  "aws-smithy-types",
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "crc32c",
  "crc32fast",
  "hex",
@@ -1315,7 +1321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cef7d0a272725f87e51ba2bf89f8c21e4df61b9e49ae1ac367a6d69916ef7c90"
 dependencies = [
  "aws-smithy-types",
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "crc32fast",
 ]
 
@@ -1328,7 +1334,7 @@ dependencies = [
  "aws-smithy-eventstream",
  "aws-smithy-runtime-api",
  "aws-smithy-types",
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "bytes-utils",
  "futures-core",
  "http 0.2.12",
@@ -1369,8 +1375,8 @@ dependencies = [
  "aws-smithy-http",
  "aws-smithy-runtime-api",
  "aws-smithy-types",
- "bytes 1.7.2",
- "fastrand 2.1.1",
+ "bytes 1.8.0",
+ "fastrand 2.2.0",
  "h2 0.3.26",
  "http 0.2.12",
  "http-body 0.4.6",
@@ -1394,7 +1400,7 @@ checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd"
 dependencies = [
  "aws-smithy-async",
  "aws-smithy-types",
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "http 0.2.12",
  "http 1.1.0",
  "pin-project-lite",
@@ -1410,7 +1416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510"
 dependencies = [
  "base64-simd",
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "bytes-utils",
  "futures-core",
  "http 0.2.12",
@@ -1431,9 +1437,9 @@ dependencies = [
 
 [[package]]
 name = "aws-smithy-xml"
-version = "0.60.8"
+version = "0.60.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d123fbc2a4adc3c301652ba8e149bf4bc1d1725affb9784eb20c953ace06bf55"
+checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc"
 dependencies = [
  "xmlparser",
 ]
@@ -1462,7 +1468,7 @@ dependencies = [
  "axum-core",
  "base64 0.21.7",
  "bitflags 1.3.2",
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "futures-util",
  "headers",
  "http 0.2.12",
@@ -1495,7 +1501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
 dependencies = [
  "async-trait",
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "futures-util",
  "http 0.2.12",
  "http-body 0.4.6",
@@ -1512,7 +1518,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f9a320103719de37b7b4da4c8eb629d4573f6bcfd3dfe80d3208806895ccf81d"
 dependencies = [
  "axum",
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "futures-util",
  "http 0.2.12",
  "mime",
@@ -1535,7 +1541,7 @@ dependencies = [
  "addr2line",
  "cfg-if",
  "libc",
- "miniz_oxide 0.8.0",
+ "miniz_oxide",
  "object",
  "rustc-demangle",
  "windows-targets 0.52.6",
@@ -1583,9 +1589,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
 
 [[package]]
 name = "bigdecimal"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee"
+checksum = "8f850665a0385e070b64c38d2354e6c104c8479c59868d1e48a0c13ee2c7a1c1"
 dependencies = [
  "autocfg",
  "libm",
@@ -1604,26 +1610,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "bindgen"
-version = "0.69.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
-dependencies = [
- "bitflags 2.6.0",
- "cexpr",
- "clang-sys",
- "itertools 0.12.1",
- "lazy_static",
- "lazycell",
- "proc-macro2",
- "quote",
- "regex",
- "rustc-hash 1.1.0",
- "shlex",
- "syn 2.0.87",
-]
-
 [[package]]
 name = "bindgen"
 version = "0.70.1"
@@ -1712,9 +1698,9 @@ dependencies = [
 
 [[package]]
 name = "bitstream-io"
-version = "2.5.3"
+version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452"
+checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
 
 [[package]]
 name = "bitvec"
@@ -1825,15 +1811,15 @@ dependencies = [
  "async-channel 2.3.1",
  "async-task",
  "futures-io",
- "futures-lite 2.3.0",
+ "futures-lite 2.5.0",
  "piper",
 ]
 
 [[package]]
 name = "borsh"
-version = "1.5.1"
+version = "1.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed"
+checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03"
 dependencies = [
  "borsh-derive",
  "cfg_aliases 0.2.1",
@@ -1841,16 +1827,15 @@ dependencies = [
 
 [[package]]
 name = "borsh-derive"
-version = "1.5.1"
+version = "1.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b"
+checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244"
 dependencies = [
  "once_cell",
  "proc-macro-crate",
  "proc-macro2",
  "quote",
  "syn 2.0.87",
- "syn_derive",
 ]
 
 [[package]]
@@ -1868,20 +1853,20 @@ dependencies = [
 
 [[package]]
 name = "bstr"
-version = "1.10.0"
+version = "1.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
+checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22"
 dependencies = [
  "memchr",
- "regex-automata 0.4.7",
+ "regex-automata 0.4.9",
  "serde",
 ]
 
 [[package]]
 name = "built"
-version = "0.7.4"
+version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4"
+checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b"
 
 [[package]]
 name = "bumpalo"
@@ -1919,18 +1904,18 @@ dependencies = [
 
 [[package]]
 name = "bytemuck"
-version = "1.17.1"
+version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2"
+checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
 dependencies = [
  "bytemuck_derive",
 ]
 
 [[package]]
 name = "bytemuck_derive"
-version = "1.7.1"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26"
+checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1961,9 +1946,9 @@ dependencies = [
 
 [[package]]
 name = "bytes"
-version = "1.7.2"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
+checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
 
 [[package]]
 name = "bytes-utils"
@@ -1971,7 +1956,7 @@ version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35"
 dependencies = [
- "bytes 1.7.2",
+ "bytes 1.8.0",
  "either",
 ]
 
@@ -1984,7 +1969,7 @@ dependencies = [
  "client",
  "collections",
  "fs",
- "futures 0.3.30",
+ "futures 0.3.31",
  "gpui",
  "http_client",
  "language",
@@ -2007,10 +1992,10 @@ checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
 dependencies = [
  "bitflags 2.6.0",
  "log",
- "polling 3.7.3",
- "rustix 0.38.35",
+ "polling 3.7.4",
+ "rustix 0.38.40",
  "slab",
- "thiserror",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
@@ -2020,7 +2005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
 dependencies = [
  "calloop",
- "rustix 0.38.35",
+ "rustix 0.38.40",
  "wayland-backend",
  "wayland-client",
 ]
@@ -2036,9 +2021,9 @@ dependencies = [
 
 [[package]]
 name = "cap-fs-ext"
-version = "3.2.0"
+version = "3.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb23061fc1c4ead4e45ca713080fe768e6234e959f5a5c399c39eb41aa34e56e"
+checksum = "e16619ada836f12897a72011fe99b03f0025b87a8dbbea4f3c9f89b458a23bf3"
 dependencies = [
  "cap-primitives",
  "cap-std",
@@ -2048,21 +2033,21 @@ dependencies = [
 
 [[package]]
 name = "cap-net-ext"
-version = "3.2.0"
+version = "3.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f83ae11f116bcbafc5327c6af250341db96b5930046732e1905f7dc65887e0e1"
+checksum = "710b0eb776410a22c89a98f2f80b2187c2ac3a8206b99f3412332e63c9b09de0"
 dependencies = [
  "cap-primitives",
  "cap-std",
- "rustix 0.38.35",
+ "rustix 0.38.40",
  "smallvec",
 ]
 
 [[package]]
 name = "cap-primitives"
-version = "3.2.0"
+version = "3.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d00bd8d26c4270d950eaaa837387964a2089a1c3c349a690a1fa03221d29531"
+checksum = "82fa6c3f9773feab88d844aa50035a33fb6e7e7426105d2f4bb7aadc42a5f89a"
 dependencies = [
  "ambient-authority",
  "fs-set-times",
@@ -2070,16 +2055,16 @@ dependencies = [
  "io-lifetimes 2.0.3",
  "ipnet",
  "maybe-owned",
- "rustix 0.38.35",
+ "rustix 0.38.40",
  "windows-sys 0.52.0",
  "winx",
 ]
 
 [[package]]
 name = "cap-rand"
-version = "3.2.0"
+version = "3.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbcb16a619d8b8211ed61f42bd290d2a1ac71277a69cf8417ec0996fa92f5211"
+checksum = "53774d49369892b70184f8312e50c1b87edccb376691de4485b0ff554b27c36c"
 dependencies = [
  "ambient-authority",
  "rand 0.8.5",
@@ -2087,27 +2072,27 @@ dependencies = [
 
 [[package]]
 name = "cap-std"
-version = "3.2.0"
+version = "3.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19eb8e3d71996828751c1ed3908a439639752ac6bdc874e41469ef7fc15fbd7f"
+checksum = "7f71b70818556b4fe2a10c7c30baac3f5f45e973f49fc2673d7c75c39d0baf5b"
 dependencies = [
  "cap-primitives",
  "io-extras",
  "io-lifetimes 2.0.3",
- "rustix 0.38.35",
+ "rustix 0.38.40",
 ]
 
 [[package]]
 name = "cap-time-ext"
-version = "3.2.0"
+version = "3.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61142dc51e25b7acc970ca578ce2c3695eac22bbba46c1073f5f583e78957725"
+checksum = "69dd48afa2363f746c93f961c211f6f099fb594a3446b8097bc5f79db51b6816"
 dependencies = [
  "ambient-authority",
  "cap-primitives",
  "iana-time-zone",
  "once_cell",
- "rustix 0.38.35",
+ "rustix 0.38.40",
  "winx",
 ]
 
@@ -2131,7 +2116,7 @@ dependencies = [
  "semver",
  "serde",
  "serde_json",
- "thiserror",
+ "thiserror 1.0.69",
 ]
 
 [[package]]
@@ -2166,7 +2151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
 dependencies = [
  "heck 0.4.1",
- "indexmap 2.4.0",
+ "indexmap 2.6.0",
  "log",
  "proc-macro2",
  "quote",
@@ -2179,9 +2164,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.1.15"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
+checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
 dependencies = [
  "jobserver",
  "libc",

Cargo.toml 🔗

@@ -368,12 +368,14 @@ indexmap = { version = "1.6.2", features = ["serde"] }
 indoc = "2"
 itertools = "0.13.0"
 jsonwebtoken = "9.3"
+jupyter-protocol = { version = "0.2.0" }
+jupyter-websocket-client = { version = "0.4.1" }
 libc = "0.2"
 linkify = "0.10.0"
 log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
 markup5ever_rcdom = "0.3.0"
 nanoid = "0.4"
-nbformat = "0.5.0"
+nbformat = "0.6.0"
 nix = "0.29"
 num-format = "0.4.4"
 once_cell = "1.19.0"
@@ -407,7 +409,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
     "stream",
 ] }
 rsa = "0.9.6"
-runtimelib = { version = "0.19.0", default-features = false, features = [
+runtimelib = { version = "0.21.0", default-features = false, features = [
     "async-dispatcher-runtime",
 ] }
 rustc-demangle = "0.1.23"

crates/quick_action_bar/src/repl_menu.rs 🔗

@@ -402,7 +402,7 @@ fn session_state(session: View<Session>, cx: &WindowContext) -> ReplMenuState {
             status: session.kernel.status(),
             ..fill_fields()
         },
-        Kernel::RunningKernel(kernel) => match &kernel.execution_state {
+        Kernel::RunningKernel(kernel) => match &kernel.execution_state() {
             ExecutionState::Idle => ReplMenuState {
                 tooltip: format!("Run code on {} ({})", kernel_name, kernel_language).into(),
                 indicator: Some(Indicator::dot().color(Color::Success)),

crates/repl/Cargo.toml 🔗

@@ -25,6 +25,8 @@ feature_flags.workspace = true
 futures.workspace = true
 gpui.workspace = true
 image.workspace = true
+jupyter-websocket-client.workspace = true
+jupyter-protocol.workspace = true
 language.workspace = true
 log.workspace = true
 markdown_preview.workspace = true

crates/repl/src/kernels/mod.rs 🔗

@@ -0,0 +1,227 @@
+mod native_kernel;
+use std::{fmt::Debug, future::Future, path::PathBuf};
+
+use futures::{
+    channel::mpsc::{self, Receiver},
+    future::Shared,
+    stream,
+};
+use gpui::{AppContext, Model, Task};
+use language::LanguageName;
+pub use native_kernel::*;
+
+mod remote_kernels;
+use project::{Project, WorktreeId};
+pub use remote_kernels::*;
+
+use anyhow::Result;
+use runtimelib::{ExecutionState, JupyterKernelspec, JupyterMessage, KernelInfoReply};
+use smol::process::Command;
+use ui::SharedString;
+
+pub type JupyterMessageChannel = stream::SelectAll<Receiver<JupyterMessage>>;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum KernelSpecification {
+    Remote(RemoteKernelSpecification),
+    Jupyter(LocalKernelSpecification),
+    PythonEnv(LocalKernelSpecification),
+}
+
+impl KernelSpecification {
+    pub fn name(&self) -> SharedString {
+        match self {
+            Self::Jupyter(spec) => spec.name.clone().into(),
+            Self::PythonEnv(spec) => spec.name.clone().into(),
+            Self::Remote(spec) => spec.name.clone().into(),
+        }
+    }
+
+    pub fn type_name(&self) -> SharedString {
+        match self {
+            Self::Jupyter(_) => "Jupyter".into(),
+            Self::PythonEnv(_) => "Python Environment".into(),
+            Self::Remote(_) => "Remote".into(),
+        }
+    }
+
+    pub fn path(&self) -> SharedString {
+        SharedString::from(match self {
+            Self::Jupyter(spec) => spec.path.to_string_lossy().to_string(),
+            Self::PythonEnv(spec) => spec.path.to_string_lossy().to_string(),
+            Self::Remote(spec) => spec.url.to_string(),
+        })
+    }
+
+    pub fn language(&self) -> SharedString {
+        SharedString::from(match self {
+            Self::Jupyter(spec) => spec.kernelspec.language.clone(),
+            Self::PythonEnv(spec) => spec.kernelspec.language.clone(),
+            Self::Remote(spec) => spec.kernelspec.language.clone(),
+        })
+    }
+}
+
+pub fn python_env_kernel_specifications(
+    project: &Model<Project>,
+    worktree_id: WorktreeId,
+    cx: &mut AppContext,
+) -> impl Future<Output = Result<Vec<KernelSpecification>>> {
+    let python_language = LanguageName::new("Python");
+    let toolchains = project
+        .read(cx)
+        .available_toolchains(worktree_id, python_language, cx);
+    let background_executor = cx.background_executor().clone();
+
+    async move {
+        let toolchains = if let Some(toolchains) = toolchains.await {
+            toolchains
+        } else {
+            return Ok(Vec::new());
+        };
+
+        let kernelspecs = toolchains.toolchains.into_iter().map(|toolchain| {
+            background_executor.spawn(async move {
+                let python_path = toolchain.path.to_string();
+
+                // Check if ipykernel is installed
+                let ipykernel_check = Command::new(&python_path)
+                    .args(&["-c", "import ipykernel"])
+                    .output()
+                    .await;
+
+                if ipykernel_check.is_ok() && ipykernel_check.unwrap().status.success() {
+                    // Create a default kernelspec for this environment
+                    let default_kernelspec = JupyterKernelspec {
+                        argv: vec![
+                            python_path.clone(),
+                            "-m".to_string(),
+                            "ipykernel_launcher".to_string(),
+                            "-f".to_string(),
+                            "{connection_file}".to_string(),
+                        ],
+                        display_name: toolchain.name.to_string(),
+                        language: "python".to_string(),
+                        interrupt_mode: None,
+                        metadata: None,
+                        env: None,
+                    };
+
+                    Some(KernelSpecification::PythonEnv(LocalKernelSpecification {
+                        name: toolchain.name.to_string(),
+                        path: PathBuf::from(&python_path),
+                        kernelspec: default_kernelspec,
+                    }))
+                } else {
+                    None
+                }
+            })
+        });
+
+        let kernel_specs = futures::future::join_all(kernelspecs)
+            .await
+            .into_iter()
+            .flatten()
+            .collect();
+
+        anyhow::Ok(kernel_specs)
+    }
+}
+
+pub trait RunningKernel: Send + Debug {
+    fn request_tx(&self) -> mpsc::Sender<JupyterMessage>;
+    fn working_directory(&self) -> &PathBuf;
+    fn execution_state(&self) -> &ExecutionState;
+    fn set_execution_state(&mut self, state: ExecutionState);
+    fn kernel_info(&self) -> Option<&KernelInfoReply>;
+    fn set_kernel_info(&mut self, info: KernelInfoReply);
+    fn force_shutdown(&mut self) -> anyhow::Result<()>;
+}
+
+#[derive(Debug, Clone)]
+pub enum KernelStatus {
+    Idle,
+    Busy,
+    Starting,
+    Error,
+    ShuttingDown,
+    Shutdown,
+    Restarting,
+}
+
+impl KernelStatus {
+    pub fn is_connected(&self) -> bool {
+        match self {
+            KernelStatus::Idle | KernelStatus::Busy => true,
+            _ => false,
+        }
+    }
+}
+
+impl ToString for KernelStatus {
+    fn to_string(&self) -> String {
+        match self {
+            KernelStatus::Idle => "Idle".to_string(),
+            KernelStatus::Busy => "Busy".to_string(),
+            KernelStatus::Starting => "Starting".to_string(),
+            KernelStatus::Error => "Error".to_string(),
+            KernelStatus::ShuttingDown => "Shutting Down".to_string(),
+            KernelStatus::Shutdown => "Shutdown".to_string(),
+            KernelStatus::Restarting => "Restarting".to_string(),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub enum Kernel {
+    RunningKernel(Box<dyn RunningKernel>),
+    StartingKernel(Shared<Task<()>>),
+    ErroredLaunch(String),
+    ShuttingDown,
+    Shutdown,
+    Restarting,
+}
+
+impl From<&Kernel> for KernelStatus {
+    fn from(kernel: &Kernel) -> Self {
+        match kernel {
+            Kernel::RunningKernel(kernel) => match kernel.execution_state() {
+                ExecutionState::Idle => KernelStatus::Idle,
+                ExecutionState::Busy => KernelStatus::Busy,
+            },
+            Kernel::StartingKernel(_) => KernelStatus::Starting,
+            Kernel::ErroredLaunch(_) => KernelStatus::Error,
+            Kernel::ShuttingDown => KernelStatus::ShuttingDown,
+            Kernel::Shutdown => KernelStatus::Shutdown,
+            Kernel::Restarting => KernelStatus::Restarting,
+        }
+    }
+}
+
+impl Kernel {
+    pub fn status(&self) -> KernelStatus {
+        self.into()
+    }
+
+    pub fn set_execution_state(&mut self, status: &ExecutionState) {
+        if let Kernel::RunningKernel(running_kernel) = self {
+            running_kernel.set_execution_state(status.clone());
+        }
+    }
+
+    pub fn set_kernel_info(&mut self, kernel_info: &KernelInfoReply) {
+        if let Kernel::RunningKernel(running_kernel) = self {
+            running_kernel.set_kernel_info(kernel_info.clone());
+        }
+    }
+
+    pub fn is_shutting_down(&self) -> bool {
+        match self {
+            Kernel::Restarting | Kernel::ShuttingDown => true,
+            Kernel::RunningKernel(_)
+            | Kernel::StartingKernel(_)
+            | Kernel::ErroredLaunch(_)
+            | Kernel::Shutdown => false,
+        }
+    }
+}

crates/repl/src/kernels.rs → crates/repl/src/kernels/native_kernel.rs 🔗

@@ -1,69 +1,24 @@
 use anyhow::{Context as _, Result};
 use futures::{
-    channel::mpsc::{self, Receiver},
-    future::Shared,
-    stream::{self, SelectAll, StreamExt},
+    channel::mpsc::{self},
+    stream::{SelectAll, StreamExt},
     SinkExt as _,
 };
-use gpui::{AppContext, EntityId, Model, Task};
-use language::LanguageName;
-use project::{Fs, Project, WorktreeId};
-use runtimelib::{
-    dirs, ConnectionInfo, ExecutionState, JupyterKernelspec, JupyterMessage, JupyterMessageContent,
-    KernelInfoReply,
-};
+use gpui::{AppContext, EntityId, Task};
+use jupyter_protocol::{JupyterMessage, JupyterMessageContent, KernelInfoReply};
+use project::Fs;
+use runtimelib::{dirs, ConnectionInfo, ExecutionState, JupyterKernelspec};
 use smol::{net::TcpListener, process::Command};
 use std::{
     env,
     fmt::Debug,
-    future::Future,
     net::{IpAddr, Ipv4Addr, SocketAddr},
     path::PathBuf,
     sync::Arc,
 };
-use ui::SharedString;
 use uuid::Uuid;
 
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum KernelSpecification {
-    Remote(RemoteKernelSpecification),
-    Jupyter(LocalKernelSpecification),
-    PythonEnv(LocalKernelSpecification),
-}
-
-impl KernelSpecification {
-    pub fn name(&self) -> SharedString {
-        match self {
-            Self::Jupyter(spec) => spec.name.clone().into(),
-            Self::PythonEnv(spec) => spec.name.clone().into(),
-            Self::Remote(spec) => spec.name.clone().into(),
-        }
-    }
-
-    pub fn type_name(&self) -> SharedString {
-        match self {
-            Self::Jupyter(_) => "Jupyter".into(),
-            Self::PythonEnv(_) => "Python Environment".into(),
-            Self::Remote(_) => "Remote".into(),
-        }
-    }
-
-    pub fn path(&self) -> SharedString {
-        SharedString::from(match self {
-            Self::Jupyter(spec) => spec.path.to_string_lossy().to_string(),
-            Self::PythonEnv(spec) => spec.path.to_string_lossy().to_string(),
-            Self::Remote(spec) => spec.url.to_string(),
-        })
-    }
-
-    pub fn language(&self) -> SharedString {
-        SharedString::from(match self {
-            Self::Jupyter(spec) => spec.kernelspec.language.clone(),
-            Self::PythonEnv(spec) => spec.kernelspec.language.clone(),
-            Self::Remote(spec) => spec.kernelspec.language.clone(),
-        })
-    }
-}
+use super::{JupyterMessageChannel, RunningKernel};
 
 #[derive(Debug, Clone)]
 pub struct LocalKernelSpecification {
@@ -80,22 +35,6 @@ impl PartialEq for LocalKernelSpecification {
 
 impl Eq for LocalKernelSpecification {}
 
-#[derive(Debug, Clone)]
-pub struct RemoteKernelSpecification {
-    pub name: String,
-    pub url: String,
-    pub token: String,
-    pub kernelspec: JupyterKernelspec,
-}
-
-impl PartialEq for RemoteKernelSpecification {
-    fn eq(&self, other: &Self) -> bool {
-        self.name == other.name && self.url == other.url
-    }
-}
-
-impl Eq for RemoteKernelSpecification {}
-
 impl LocalKernelSpecification {
     #[must_use]
     fn command(&self, connection_path: &PathBuf) -> Result<Command> {
@@ -147,95 +86,7 @@ async fn peek_ports(ip: IpAddr) -> Result<[u16; 5]> {
     Ok(ports)
 }
 
-#[derive(Debug, Clone)]
-pub enum KernelStatus {
-    Idle,
-    Busy,
-    Starting,
-    Error,
-    ShuttingDown,
-    Shutdown,
-    Restarting,
-}
-
-impl KernelStatus {
-    pub fn is_connected(&self) -> bool {
-        match self {
-            KernelStatus::Idle | KernelStatus::Busy => true,
-            _ => false,
-        }
-    }
-}
-
-impl ToString for KernelStatus {
-    fn to_string(&self) -> String {
-        match self {
-            KernelStatus::Idle => "Idle".to_string(),
-            KernelStatus::Busy => "Busy".to_string(),
-            KernelStatus::Starting => "Starting".to_string(),
-            KernelStatus::Error => "Error".to_string(),
-            KernelStatus::ShuttingDown => "Shutting Down".to_string(),
-            KernelStatus::Shutdown => "Shutdown".to_string(),
-            KernelStatus::Restarting => "Restarting".to_string(),
-        }
-    }
-}
-
-impl From<&Kernel> for KernelStatus {
-    fn from(kernel: &Kernel) -> Self {
-        match kernel {
-            Kernel::RunningKernel(kernel) => match kernel.execution_state {
-                ExecutionState::Idle => KernelStatus::Idle,
-                ExecutionState::Busy => KernelStatus::Busy,
-            },
-            Kernel::StartingKernel(_) => KernelStatus::Starting,
-            Kernel::ErroredLaunch(_) => KernelStatus::Error,
-            Kernel::ShuttingDown => KernelStatus::ShuttingDown,
-            Kernel::Shutdown => KernelStatus::Shutdown,
-            Kernel::Restarting => KernelStatus::Restarting,
-        }
-    }
-}
-
-#[derive(Debug)]
-pub enum Kernel {
-    RunningKernel(RunningKernel),
-    StartingKernel(Shared<Task<()>>),
-    ErroredLaunch(String),
-    ShuttingDown,
-    Shutdown,
-    Restarting,
-}
-
-impl Kernel {
-    pub fn status(&self) -> KernelStatus {
-        self.into()
-    }
-
-    pub fn set_execution_state(&mut self, status: &ExecutionState) {
-        if let Kernel::RunningKernel(running_kernel) = self {
-            running_kernel.execution_state = status.clone();
-        }
-    }
-
-    pub fn set_kernel_info(&mut self, kernel_info: &KernelInfoReply) {
-        if let Kernel::RunningKernel(running_kernel) = self {
-            running_kernel.kernel_info = Some(kernel_info.clone());
-        }
-    }
-
-    pub fn is_shutting_down(&self) -> bool {
-        match self {
-            Kernel::Restarting | Kernel::ShuttingDown => true,
-            Kernel::RunningKernel(_)
-            | Kernel::StartingKernel(_)
-            | Kernel::ErroredLaunch(_)
-            | Kernel::Shutdown => false,
-        }
-    }
-}
-
-pub struct RunningKernel {
+pub struct NativeRunningKernel {
     pub process: smol::process::Child,
     _shell_task: Task<Result<()>>,
     _iopub_task: Task<Result<()>>,
@@ -248,9 +99,7 @@ pub struct RunningKernel {
     pub kernel_info: Option<KernelInfoReply>,
 }
 
-type JupyterMessageChannel = stream::SelectAll<Receiver<JupyterMessage>>;
-
-impl Debug for RunningKernel {
+impl Debug for NativeRunningKernel {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("RunningKernel")
             .field("process", &self.process)
@@ -258,25 +107,14 @@ impl Debug for RunningKernel {
     }
 }
 
-impl RunningKernel {
+impl NativeRunningKernel {
     pub fn new(
-        kernel_specification: KernelSpecification,
+        kernel_specification: LocalKernelSpecification,
         entity_id: EntityId,
         working_directory: PathBuf,
         fs: Arc<dyn Fs>,
         cx: &mut AppContext,
     ) -> Task<Result<(Self, JupyterMessageChannel)>> {
-        let kernel_specification = match kernel_specification {
-            KernelSpecification::Jupyter(spec) => spec,
-            KernelSpecification::PythonEnv(spec) => spec,
-            KernelSpecification::Remote(_spec) => {
-                // todo!(): Implement remote kernel specification
-                return Task::ready(Err(anyhow::anyhow!(
-                    "Running remote kernels is not supported"
-                )));
-            }
-        };
-
         cx.spawn(|cx| async move {
             let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
             let ports = peek_ports(ip).await?;
@@ -315,15 +153,13 @@ impl RunningKernel {
 
             let session_id = Uuid::new_v4().to_string();
 
-            let mut iopub_socket = connection_info
-                .create_client_iopub_connection("", &session_id)
-                .await?;
-            let mut shell_socket = connection_info
-                .create_client_shell_connection(&session_id)
-                .await?;
-            let mut control_socket = connection_info
-                .create_client_control_connection(&session_id)
-                .await?;
+            let mut iopub_socket =
+                runtimelib::create_client_iopub_connection(&connection_info, "", &session_id)
+                    .await?;
+            let mut shell_socket =
+                runtimelib::create_client_shell_connection(&connection_info, &session_id).await?;
+            let mut control_socket =
+                runtimelib::create_client_control_connection(&connection_info, &session_id).await?;
 
             let (mut iopub, iosub) = futures::channel::mpsc::channel(100);
 
@@ -410,7 +246,43 @@ impl RunningKernel {
     }
 }
 
-impl Drop for RunningKernel {
+impl RunningKernel for NativeRunningKernel {
+    fn request_tx(&self) -> mpsc::Sender<JupyterMessage> {
+        self.request_tx.clone()
+    }
+
+    fn working_directory(&self) -> &PathBuf {
+        &self.working_directory
+    }
+
+    fn execution_state(&self) -> &ExecutionState {
+        &self.execution_state
+    }
+
+    fn set_execution_state(&mut self, state: ExecutionState) {
+        self.execution_state = state;
+    }
+
+    fn kernel_info(&self) -> Option<&KernelInfoReply> {
+        self.kernel_info.as_ref()
+    }
+
+    fn set_kernel_info(&mut self, info: KernelInfoReply) {
+        self.kernel_info = Some(info);
+    }
+
+    fn force_shutdown(&mut self) -> anyhow::Result<()> {
+        match self.process.kill() {
+            Ok(_) => Ok(()),
+            Err(error) => Err(anyhow::anyhow!(
+                "Failed to kill the kernel process: {}",
+                error
+            )),
+        }
+    }
+}
+
+impl Drop for NativeRunningKernel {
     fn drop(&mut self) {
         std::fs::remove_file(&self.connection_path).ok();
         self.request_tx.close_channel();
@@ -467,72 +339,6 @@ async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result<Vec<LocalKernelS
     Ok(valid_kernelspecs)
 }
 
-pub fn python_env_kernel_specifications(
-    project: &Model<Project>,
-    worktree_id: WorktreeId,
-    cx: &mut AppContext,
-) -> impl Future<Output = Result<Vec<KernelSpecification>>> {
-    let python_language = LanguageName::new("Python");
-    let toolchains = project
-        .read(cx)
-        .available_toolchains(worktree_id, python_language, cx);
-    let background_executor = cx.background_executor().clone();
-
-    async move {
-        let toolchains = if let Some(toolchains) = toolchains.await {
-            toolchains
-        } else {
-            return Ok(Vec::new());
-        };
-
-        let kernelspecs = toolchains.toolchains.into_iter().map(|toolchain| {
-            background_executor.spawn(async move {
-                let python_path = toolchain.path.to_string();
-
-                // Check if ipykernel is installed
-                let ipykernel_check = Command::new(&python_path)
-                    .args(&["-c", "import ipykernel"])
-                    .output()
-                    .await;
-
-                if ipykernel_check.is_ok() && ipykernel_check.unwrap().status.success() {
-                    // Create a default kernelspec for this environment
-                    let default_kernelspec = JupyterKernelspec {
-                        argv: vec![
-                            python_path.clone(),
-                            "-m".to_string(),
-                            "ipykernel_launcher".to_string(),
-                            "-f".to_string(),
-                            "{connection_file}".to_string(),
-                        ],
-                        display_name: toolchain.name.to_string(),
-                        language: "python".to_string(),
-                        interrupt_mode: None,
-                        metadata: None,
-                        env: None,
-                    };
-
-                    Some(KernelSpecification::PythonEnv(LocalKernelSpecification {
-                        name: toolchain.name.to_string(),
-                        path: PathBuf::from(&python_path),
-                        kernelspec: default_kernelspec,
-                    }))
-                } else {
-                    None
-                }
-            })
-        });
-
-        let kernel_specs = futures::future::join_all(kernelspecs)
-            .await
-            .into_iter()
-            .flatten()
-            .collect();
-
-        anyhow::Ok(kernel_specs)
-    }
-}
-
 pub async fn local_kernel_specifications(fs: Arc<dyn Fs>) -> Result<Vec<LocalKernelSpecification>> {
     let mut data_dirs = dirs::data_dirs();
 

crates/repl/src/kernels/remote_kernels.rs 🔗

@@ -0,0 +1,122 @@
+use futures::{channel::mpsc, StreamExt as _};
+use gpui::AppContext;
+use jupyter_protocol::{ExecutionState, JupyterMessage, KernelInfoReply};
+// todo(kyle): figure out if this needs to be different
+use runtimelib::JupyterKernelspec;
+
+use super::RunningKernel;
+use jupyter_websocket_client::RemoteServer;
+use std::fmt::Debug;
+
+#[derive(Debug, Clone)]
+pub struct RemoteKernelSpecification {
+    pub name: String,
+    pub url: String,
+    pub token: String,
+    pub kernelspec: JupyterKernelspec,
+}
+
+impl PartialEq for RemoteKernelSpecification {
+    fn eq(&self, other: &Self) -> bool {
+        self.name == other.name && self.url == other.url
+    }
+}
+
+impl Eq for RemoteKernelSpecification {}
+
+pub struct RemoteRunningKernel {
+    remote_server: RemoteServer,
+    pub working_directory: std::path::PathBuf,
+    pub request_tx: mpsc::Sender<JupyterMessage>,
+    pub execution_state: ExecutionState,
+    pub kernel_info: Option<KernelInfoReply>,
+}
+
+impl RemoteRunningKernel {
+    pub async fn new(
+        kernelspec: RemoteKernelSpecification,
+        working_directory: std::path::PathBuf,
+        request_tx: mpsc::Sender<JupyterMessage>,
+        _cx: &mut AppContext,
+    ) -> anyhow::Result<(
+        Self,
+        (), // Stream<Item=JupyterMessage>
+    )> {
+        let remote_server = RemoteServer {
+            base_url: kernelspec.url,
+            token: kernelspec.token,
+        };
+
+        // todo: launch a kernel to get a kernel ID
+        let kernel_id = "not-implemented";
+
+        let kernel_socket = remote_server.connect_to_kernel(kernel_id).await?;
+
+        let (mut _w, mut _r) = kernel_socket.split();
+
+        let (_messages_tx, _messages_rx) = mpsc::channel::<JupyterMessage>(100);
+
+        // let routing_task = cx.background_executor().spawn({
+        //     async move {
+        //         while let Some(message) = request_rx.next().await {
+        //             w.send(message).await;
+        //         }
+        //     }
+        // });
+        // let messages_rx = r.into();
+
+        anyhow::Ok((
+            Self {
+                remote_server,
+                working_directory,
+                request_tx,
+                execution_state: ExecutionState::Idle,
+                kernel_info: None,
+            },
+            (),
+        ))
+    }
+}
+
+impl Debug for RemoteRunningKernel {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("RemoteRunningKernel")
+            // custom debug that keeps tokens out of logs
+            .field("remote_server url", &self.remote_server.base_url)
+            .field("working_directory", &self.working_directory)
+            .field("request_tx", &self.request_tx)
+            .field("execution_state", &self.execution_state)
+            .field("kernel_info", &self.kernel_info)
+            .finish()
+    }
+}
+
+impl RunningKernel for RemoteRunningKernel {
+    fn request_tx(&self) -> futures::channel::mpsc::Sender<runtimelib::JupyterMessage> {
+        self.request_tx.clone()
+    }
+
+    fn working_directory(&self) -> &std::path::PathBuf {
+        &self.working_directory
+    }
+
+    fn execution_state(&self) -> &runtimelib::ExecutionState {
+        &self.execution_state
+    }
+
+    fn set_execution_state(&mut self, state: runtimelib::ExecutionState) {
+        self.execution_state = state;
+    }
+
+    fn kernel_info(&self) -> Option<&runtimelib::KernelInfoReply> {
+        self.kernel_info.as_ref()
+    }
+
+    fn set_kernel_info(&mut self, info: runtimelib::KernelInfoReply) {
+        self.kernel_info = Some(info);
+    }
+
+    fn force_shutdown(&mut self) -> anyhow::Result<()> {
+        unimplemented!("force_shutdown")
+    }
+}

crates/repl/src/repl.rs 🔗

@@ -1,6 +1,6 @@
 pub mod components;
 mod jupyter_settings;
-mod kernels;
+pub mod kernels;
 pub mod notebook;
 mod outputs;
 mod repl_editor;

crates/repl/src/session.rs 🔗

@@ -1,7 +1,7 @@
 use crate::components::KernelListItem;
 use crate::setup_editor_session_actions;
 use crate::{
-    kernels::{Kernel, KernelSpecification, RunningKernel},
+    kernels::{Kernel, KernelSpecification, NativeRunningKernel},
     outputs::{ExecutionStatus, ExecutionView},
     KernelStatus,
 };
@@ -246,13 +246,19 @@ impl Session {
             cx.entity_id().to_string(),
         );
 
-        let kernel = RunningKernel::new(
-            self.kernel_specification.clone(),
-            entity_id,
-            working_directory,
-            self.fs.clone(),
-            cx,
-        );
+        let kernel = match self.kernel_specification.clone() {
+            KernelSpecification::Jupyter(kernel_specification)
+            | KernelSpecification::PythonEnv(kernel_specification) => NativeRunningKernel::new(
+                kernel_specification,
+                entity_id,
+                working_directory,
+                self.fs.clone(),
+                cx,
+            ),
+            KernelSpecification::Remote(_remote_kernel_specification) => {
+                unimplemented!()
+            }
+        };
 
         let pending_kernel = cx
             .spawn(|this, mut cx| async move {
@@ -291,7 +297,7 @@ impl Session {
                             .detach();
 
                             let status = kernel.process.status();
-                            session.kernel(Kernel::RunningKernel(kernel), cx);
+                            session.kernel(Kernel::RunningKernel(Box::new(kernel)), cx);
 
                             let process_status_task = cx.spawn(|session, mut cx| async move {
                                 let error_message = match status.await {
@@ -416,7 +422,7 @@ impl Session {
 
     fn send(&mut self, message: JupyterMessage, _cx: &mut ViewContext<Self>) -> anyhow::Result<()> {
         if let Kernel::RunningKernel(kernel) = &mut self.kernel {
-            kernel.request_tx.try_send(message).ok();
+            kernel.request_tx().try_send(message).ok();
         }
 
         anyhow::Ok(())
@@ -631,7 +637,7 @@ impl Session {
 
         match kernel {
             Kernel::RunningKernel(mut kernel) => {
-                let mut request_tx = kernel.request_tx.clone();
+                let mut request_tx = kernel.request_tx().clone();
 
                 cx.spawn(|this, mut cx| async move {
                     let message: JupyterMessage = ShutdownRequest { restart: false }.into();
@@ -646,7 +652,7 @@ impl Session {
                     })
                     .ok();
 
-                    kernel.process.kill().ok();
+                    kernel.force_shutdown().ok();
 
                     this.update(&mut cx, |session, cx| {
                         session.clear_outputs(cx);
@@ -674,7 +680,7 @@ impl Session {
                 // Do nothing if already restarting
             }
             Kernel::RunningKernel(mut kernel) => {
-                let mut request_tx = kernel.request_tx.clone();
+                let mut request_tx = kernel.request_tx().clone();
 
                 cx.spawn(|this, mut cx| async move {
                     // Send shutdown request with restart flag
@@ -692,7 +698,7 @@ impl Session {
                     cx.background_executor().timer(Duration::from_secs(1)).await;
 
                     // Force kill the kernel if it hasn't shut down
-                    kernel.process.kill().ok();
+                    kernel.force_shutdown().ok();
 
                     // Start a new kernel
                     this.update(&mut cx, |session, cx| {
@@ -727,7 +733,7 @@ impl Render for Session {
         let (status_text, interrupt_button) = match &self.kernel {
             Kernel::RunningKernel(kernel) => (
                 kernel
-                    .kernel_info
+                    .kernel_info()
                     .as_ref()
                     .map(|info| info.language_info.name.clone()),
                 Some(
@@ -747,7 +753,7 @@ impl Render for Session {
 
         KernelListItem::new(self.kernel_specification.clone())
             .status_color(match &self.kernel {
-                Kernel::RunningKernel(kernel) => match kernel.execution_state {
+                Kernel::RunningKernel(kernel) => match kernel.execution_state() {
                     ExecutionState::Idle => Color::Success,
                     ExecutionState::Busy => Color::Modified,
                 },