Skip to content

Commit

Permalink
repl: Iterate on design of REPL sessions view (zed-industries#14987)
Browse files Browse the repository at this point in the history
This PR iterates on the design of the REPL sessions view.

We now use the same component for both available kernels and running
ones to provide some consistency between the two modes:

<img width="1208" alt="Screenshot 2024-07-22 at 6 49 08 PM"
src="https://github.com/user-attachments/assets/8b5c3600-e438-49fa-8484-cefabf4b44f1">

<img width="1208" alt="Screenshot 2024-07-22 at 6 49 14 PM"
src="https://github.com/user-attachments/assets/5125e9b3-6465-4d1e-9036-e6ca270dedcb">

Release Notes:

- N/A
  • Loading branch information
maxdeviant authored Jul 22, 2024
1 parent 01392c1 commit fe1f55c
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 117 deletions.
3 changes: 3 additions & 0 deletions crates/repl/src/components.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod kernel_list_item;

pub use kernel_list_item::*;
60 changes: 60 additions & 0 deletions crates/repl/src/components/kernel_list_item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use gpui::AnyElement;
use ui::{prelude::*, Indicator, ListItem};

use crate::KernelSpecification;

#[derive(IntoElement)]
pub struct KernelListItem {
kernel_specification: KernelSpecification,
status_color: Color,
buttons: Vec<AnyElement>,
children: Vec<AnyElement>,
}

impl KernelListItem {
pub fn new(kernel_specification: KernelSpecification) -> Self {
Self {
kernel_specification,
status_color: Color::Disabled,
buttons: Vec::new(),
children: Vec::new(),
}
}

pub fn status_color(mut self, color: Color) -> Self {
self.status_color = color;
self
}

pub fn button(mut self, button: impl IntoElement) -> Self {
self.buttons.push(button.into_any_element());
self
}

pub fn buttons(mut self, buttons: impl IntoIterator<Item = impl IntoElement>) -> Self {
self.buttons
.extend(buttons.into_iter().map(|button| button.into_any_element()));
self
}
}

impl ParentElement for KernelListItem {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}

impl RenderOnce for KernelListItem {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
ListItem::new(SharedString::from(self.kernel_specification.name.clone()))
.selectable(false)
.start_slot(
h_flex()
.size_3()
.justify_center()
.child(Indicator::dot().color(self.status_color)),
)
.children(self.children)
.end_slot(h_flex().gap_2().children(self.buttons))
}
}
43 changes: 20 additions & 23 deletions crates/repl/src/kernels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use std::{
path::PathBuf,
sync::Arc,
};
use ui::{Color, Indicator};

#[derive(Debug, Clone)]
pub struct KernelSpecification {
Expand Down Expand Up @@ -72,15 +71,6 @@ async fn peek_ports(ip: IpAddr) -> Result<[u16; 5]> {
Ok(ports)
}

#[derive(Debug)]
pub enum Kernel {
RunningKernel(RunningKernel),
StartingKernel(Shared<Task<()>>),
ErroredLaunch(String),
ShuttingDown,
Shutdown,
}

#[derive(Debug, Clone)]
pub enum KernelStatus {
Idle,
Expand All @@ -90,6 +80,7 @@ pub enum KernelStatus {
ShuttingDown,
Shutdown,
}

impl KernelStatus {
pub fn is_connected(&self) -> bool {
match self {
Expand Down Expand Up @@ -127,20 +118,16 @@ impl From<&Kernel> for KernelStatus {
}
}

impl Kernel {
pub fn dot(&self) -> Indicator {
match self {
Kernel::RunningKernel(kernel) => match kernel.execution_state {
ExecutionState::Idle => Indicator::dot().color(Color::Success),
ExecutionState::Busy => Indicator::dot().color(Color::Modified),
},
Kernel::StartingKernel(_) => Indicator::dot().color(Color::Modified),
Kernel::ErroredLaunch(_) => Indicator::dot().color(Color::Error),
Kernel::ShuttingDown => Indicator::dot().color(Color::Modified),
Kernel::Shutdown => Indicator::dot().color(Color::Disabled),
}
}
#[derive(Debug)]
pub enum Kernel {
RunningKernel(RunningKernel),
StartingKernel(Shared<Task<()>>),
ErroredLaunch(String),
ShuttingDown,
Shutdown,
}

impl Kernel {
pub fn status(&self) -> KernelStatus {
self.into()
}
Expand All @@ -162,6 +149,16 @@ impl Kernel {
_ => {}
}
}

pub fn is_shutting_down(&self) -> bool {
match self {
Kernel::ShuttingDown => true,
Kernel::RunningKernel(_)
| Kernel::StartingKernel(_)
| Kernel::ErroredLaunch(_)
| Kernel::Shutdown => false,
}
}
}

pub struct RunningKernel {
Expand Down
25 changes: 14 additions & 11 deletions crates/repl/src/repl.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
use async_dispatcher::{set_dispatcher, Dispatcher, Runnable};
use gpui::{AppContext, PlatformDispatcher};
use project::Fs;
use settings::Settings as _;
use std::{sync::Arc, time::Duration};

mod components;
mod jupyter_settings;
mod kernels;
mod outputs;
Expand All @@ -13,14 +8,22 @@ mod repl_store;
mod session;
mod stdio;

pub use jupyter_settings::JupyterSettings;
pub use kernels::{Kernel, KernelSpecification, KernelStatus};
pub use repl_editor::*;
pub use repl_sessions_ui::{ClearOutputs, Interrupt, ReplSessionsPage, Run, Sessions, Shutdown};
use std::{sync::Arc, time::Duration};

use async_dispatcher::{set_dispatcher, Dispatcher, Runnable};
use gpui::{AppContext, PlatformDispatcher};
use project::Fs;
pub use runtimelib::ExecutionState;
pub use session::Session;
use settings::Settings as _;

pub use crate::jupyter_settings::JupyterSettings;
pub use crate::kernels::{Kernel, KernelSpecification, KernelStatus};
pub use crate::repl_editor::*;
pub use crate::repl_sessions_ui::{
ClearOutputs, Interrupt, ReplSessionsPage, Run, Sessions, Shutdown,
};
use crate::repl_store::ReplStore;
pub use crate::session::Session;

fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
struct ZedDispatcher {
Expand Down
93 changes: 52 additions & 41 deletions crates/repl/src/repl_sessions_ui.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use editor::Editor;
use gpui::{
actions, prelude::*, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription, View,
actions, prelude::*, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView,
Subscription, View,
};
use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding};
use util::ResultExt as _;
use workspace::item::ItemEvent;
use workspace::WorkspaceId;
use workspace::{item::Item, Workspace};

use crate::components::KernelListItem;
use crate::jupyter_settings::JupyterSettings;
use crate::repl_store::ReplStore;

Expand Down Expand Up @@ -187,15 +189,10 @@ impl Render for ReplSessionsPage {
// install kernels. It can be assumed they don't have a running kernel if we have no
// specifications.
if kernel_specifications.is_empty() {
return v_flex()
.p_4()
.size_full()
.gap_2()
.child(Label::new("No Jupyter Kernels Available").size(LabelSize::Large))
.child(
Label::new("To start interactively running code in your editor, you need to install and configure Jupyter kernels.")
.size(LabelSize::Default),
)
let instructions = "To start interactively running code in your editor, you need to install and configure Jupyter kernels.";

return ReplSessionsContainer::new("No Jupyter Kernels Available")
.child(Label::new(instructions))
.child(
h_flex().w_full().p_4().justify_center().gap_2().child(
ButtonLike::new("install-kernels")
Expand All @@ -209,48 +206,62 @@ impl Render for ReplSessionsPage {
)
}),
),
)
.into_any_element();
);
}

// When there are no sessions, show the command to run code in an editor
if sessions.is_empty() {
return v_flex()
.p_4()
.size_full()
.gap_2()
.child(Label::new("No Jupyter Kernel Sessions").size(LabelSize::Large))
let instructions = "To run code in a Jupyter kernel, select some code and use the 'repl::Run' command.";

return ReplSessionsContainer::new("No Jupyter Kernel Sessions")
.child(
v_flex().child(
Label::new("To run code in a Jupyter kernel, select some code and use the 'repl::Run' command.")
.size(LabelSize::Default)
)
.children(
KeyBinding::for_action(&Run, cx)
.map(|binding|
binding.into_any_element()
)
)
v_flex()
.child(Label::new(instructions))
.children(KeyBinding::for_action(&Run, cx)),
)
.child(Label::new("Kernels available").size(LabelSize::Large))
.children(
kernel_specifications.into_iter().map(|spec| {
h_flex().gap_2().child(Label::new(spec.name.clone()))
.child(Label::new(spec.kernelspec.language.clone()).color(Color::Muted))
})
)
.children(kernel_specifications.into_iter().map(|spec| {
KernelListItem::new(spec.clone()).child(
h_flex()
.gap_2()
.child(Label::new(spec.name))
.child(Label::new(spec.kernelspec.language).color(Color::Muted)),
)
}));
}

.into_any_element();
ReplSessionsContainer::new("Jupyter Kernel Sessions").children(sessions)
}
}

#[derive(IntoElement)]
struct ReplSessionsContainer {
title: SharedString,
children: Vec<AnyElement>,
}

impl ReplSessionsContainer {
pub fn new(title: impl Into<SharedString>) -> Self {
Self {
title: title.into(),
children: Vec::new(),
}
}
}

impl ParentElement for ReplSessionsContainer {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements)
}
}

impl RenderOnce for ReplSessionsContainer {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
v_flex()
.p_4()
.child(Label::new("Jupyter Kernel Sessions").size(LabelSize::Large))
.children(
sessions
.into_iter()
.map(|session| session.clone().into_any_element()),
)
.into_any_element()
.gap_2()
.size_full()
.child(Label::new(self.title).size(LabelSize::Large))
.children(self.children)
}
}
Loading

0 comments on commit fe1f55c

Please sign in to comment.