repl: Iterate on design of REPL sessions view (#14987)
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
This commit is contained in:
parent
01392c1329
commit
fe1f55cbfd
6 changed files with 192 additions and 121 deletions
3
crates/repl/src/components.rs
Normal file
3
crates/repl/src/components.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
mod kernel_list_item;
|
||||||
|
|
||||||
|
pub use kernel_list_item::*;
|
60
crates/repl/src/components/kernel_list_item.rs
Normal file
60
crates/repl/src/components/kernel_list_item.rs
Normal file
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,6 @@ use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use ui::{Color, Indicator};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct KernelSpecification {
|
pub struct KernelSpecification {
|
||||||
|
@ -72,15 +71,6 @@ async fn peek_ports(ip: IpAddr) -> Result<[u16; 5]> {
|
||||||
Ok(ports)
|
Ok(ports)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Kernel {
|
|
||||||
RunningKernel(RunningKernel),
|
|
||||||
StartingKernel(Shared<Task<()>>),
|
|
||||||
ErroredLaunch(String),
|
|
||||||
ShuttingDown,
|
|
||||||
Shutdown,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum KernelStatus {
|
pub enum KernelStatus {
|
||||||
Idle,
|
Idle,
|
||||||
|
@ -90,6 +80,7 @@ pub enum KernelStatus {
|
||||||
ShuttingDown,
|
ShuttingDown,
|
||||||
Shutdown,
|
Shutdown,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KernelStatus {
|
impl KernelStatus {
|
||||||
pub fn is_connected(&self) -> bool {
|
pub fn is_connected(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
@ -127,20 +118,16 @@ impl From<&Kernel> for KernelStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Kernel {
|
#[derive(Debug)]
|
||||||
pub fn dot(&self) -> Indicator {
|
pub enum Kernel {
|
||||||
match self {
|
RunningKernel(RunningKernel),
|
||||||
Kernel::RunningKernel(kernel) => match kernel.execution_state {
|
StartingKernel(Shared<Task<()>>),
|
||||||
ExecutionState::Idle => Indicator::dot().color(Color::Success),
|
ErroredLaunch(String),
|
||||||
ExecutionState::Busy => Indicator::dot().color(Color::Modified),
|
ShuttingDown,
|
||||||
},
|
Shutdown,
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
impl Kernel {
|
||||||
pub fn status(&self) -> KernelStatus {
|
pub fn status(&self) -> KernelStatus {
|
||||||
self.into()
|
self.into()
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
pub struct RunningKernel {
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
use async_dispatcher::{set_dispatcher, Dispatcher, Runnable};
|
mod components;
|
||||||
use gpui::{AppContext, PlatformDispatcher};
|
|
||||||
use project::Fs;
|
|
||||||
use settings::Settings as _;
|
|
||||||
use std::{sync::Arc, time::Duration};
|
|
||||||
|
|
||||||
mod jupyter_settings;
|
mod jupyter_settings;
|
||||||
mod kernels;
|
mod kernels;
|
||||||
mod outputs;
|
mod outputs;
|
||||||
|
@ -13,14 +8,22 @@ mod repl_store;
|
||||||
mod session;
|
mod session;
|
||||||
mod stdio;
|
mod stdio;
|
||||||
|
|
||||||
pub use jupyter_settings::JupyterSettings;
|
use std::{sync::Arc, time::Duration};
|
||||||
pub use kernels::{Kernel, KernelSpecification, KernelStatus};
|
|
||||||
pub use repl_editor::*;
|
|
||||||
pub use repl_sessions_ui::{ClearOutputs, Interrupt, ReplSessionsPage, Run, Sessions, Shutdown};
|
|
||||||
pub use runtimelib::ExecutionState;
|
|
||||||
pub use session::Session;
|
|
||||||
|
|
||||||
|
use async_dispatcher::{set_dispatcher, Dispatcher, Runnable};
|
||||||
|
use gpui::{AppContext, PlatformDispatcher};
|
||||||
|
use project::Fs;
|
||||||
|
pub use runtimelib::ExecutionState;
|
||||||
|
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;
|
use crate::repl_store::ReplStore;
|
||||||
|
pub use crate::session::Session;
|
||||||
|
|
||||||
fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
|
fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
|
||||||
struct ZedDispatcher {
|
struct ZedDispatcher {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{
|
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 ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
@ -8,6 +9,7 @@ use workspace::item::ItemEvent;
|
||||||
use workspace::WorkspaceId;
|
use workspace::WorkspaceId;
|
||||||
use workspace::{item::Item, Workspace};
|
use workspace::{item::Item, Workspace};
|
||||||
|
|
||||||
|
use crate::components::KernelListItem;
|
||||||
use crate::jupyter_settings::JupyterSettings;
|
use crate::jupyter_settings::JupyterSettings;
|
||||||
use crate::repl_store::ReplStore;
|
use crate::repl_store::ReplStore;
|
||||||
|
|
||||||
|
@ -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
|
// install kernels. It can be assumed they don't have a running kernel if we have no
|
||||||
// specifications.
|
// specifications.
|
||||||
if kernel_specifications.is_empty() {
|
if kernel_specifications.is_empty() {
|
||||||
return v_flex()
|
let instructions = "To start interactively running code in your editor, you need to install and configure Jupyter kernels.";
|
||||||
.p_4()
|
|
||||||
.size_full()
|
return ReplSessionsContainer::new("No Jupyter Kernels Available")
|
||||||
.gap_2()
|
.child(Label::new(instructions))
|
||||||
.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),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
h_flex().w_full().p_4().justify_center().gap_2().child(
|
h_flex().w_full().p_4().justify_center().gap_2().child(
|
||||||
ButtonLike::new("install-kernels")
|
ButtonLike::new("install-kernels")
|
||||||
|
@ -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
|
// When there are no sessions, show the command to run code in an editor
|
||||||
if sessions.is_empty() {
|
if sessions.is_empty() {
|
||||||
return v_flex()
|
let instructions = "To run code in a Jupyter kernel, select some code and use the 'repl::Run' command.";
|
||||||
.p_4()
|
|
||||||
.size_full()
|
return ReplSessionsContainer::new("No Jupyter Kernel Sessions")
|
||||||
.gap_2()
|
|
||||||
.child(Label::new("No Jupyter Kernel Sessions").size(LabelSize::Large))
|
|
||||||
.child(
|
.child(
|
||||||
v_flex().child(
|
v_flex()
|
||||||
Label::new("To run code in a Jupyter kernel, select some code and use the 'repl::Run' command.")
|
.child(Label::new(instructions))
|
||||||
.size(LabelSize::Default)
|
.children(KeyBinding::for_action(&Run, cx)),
|
||||||
)
|
|
||||||
.children(
|
|
||||||
KeyBinding::for_action(&Run, cx)
|
|
||||||
.map(|binding|
|
|
||||||
binding.into_any_element()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.child(Label::new("Kernels available").size(LabelSize::Large))
|
.child(Label::new("Kernels available").size(LabelSize::Large))
|
||||||
.children(
|
.children(kernel_specifications.into_iter().map(|spec| {
|
||||||
kernel_specifications.into_iter().map(|spec| {
|
KernelListItem::new(spec.clone()).child(
|
||||||
h_flex().gap_2().child(Label::new(spec.name.clone()))
|
h_flex()
|
||||||
.child(Label::new(spec.kernelspec.language.clone()).color(Color::Muted))
|
.gap_2()
|
||||||
})
|
.child(Label::new(spec.name))
|
||||||
|
.child(Label::new(spec.kernelspec.language).color(Color::Muted)),
|
||||||
)
|
)
|
||||||
|
}));
|
||||||
.into_any_element();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
v_flex()
|
ReplSessionsContainer::new("Jupyter Kernel Sessions").children(sessions)
|
||||||
.p_4()
|
}
|
||||||
.child(Label::new("Jupyter Kernel Sessions").size(LabelSize::Large))
|
}
|
||||||
.children(
|
|
||||||
sessions
|
#[derive(IntoElement)]
|
||||||
.into_iter()
|
struct ReplSessionsContainer {
|
||||||
.map(|session| session.clone().into_any_element()),
|
title: SharedString,
|
||||||
)
|
children: Vec<AnyElement>,
|
||||||
.into_any_element()
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
.gap_2()
|
||||||
|
.size_full()
|
||||||
|
.child(Label::new(self.title).size(LabelSize::Large))
|
||||||
|
.children(self.children)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::components::KernelListItem;
|
||||||
use crate::{
|
use crate::{
|
||||||
kernels::{Kernel, KernelSpecification, RunningKernel},
|
kernels::{Kernel, KernelSpecification, RunningKernel},
|
||||||
outputs::{ExecutionStatus, ExecutionView, LineHeight as _},
|
outputs::{ExecutionStatus, ExecutionView, LineHeight as _},
|
||||||
|
@ -19,12 +20,13 @@ use gpui::{
|
||||||
use language::Point;
|
use language::Point;
|
||||||
use project::Fs;
|
use project::Fs;
|
||||||
use runtimelib::{
|
use runtimelib::{
|
||||||
ExecuteRequest, InterruptRequest, JupyterMessage, JupyterMessageContent, ShutdownRequest,
|
ExecuteRequest, ExecutionState, InterruptRequest, JupyterMessage, JupyterMessageContent,
|
||||||
|
ShutdownRequest,
|
||||||
};
|
};
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use std::{env::temp_dir, ops::Range, sync::Arc, time::Duration};
|
use std::{env::temp_dir, ops::Range, sync::Arc, time::Duration};
|
||||||
use theme::{ActiveTheme, ThemeSettings};
|
use theme::{ActiveTheme, ThemeSettings};
|
||||||
use ui::{h_flex, prelude::*, v_flex, ButtonLike, ButtonStyle, IconButtonShape, Label, Tooltip};
|
use ui::{prelude::*, IconButtonShape, Tooltip};
|
||||||
|
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
editor: WeakView<Editor>,
|
editor: WeakView<Editor>,
|
||||||
|
@ -561,52 +563,47 @@ impl EventEmitter<SessionEvent> for Session {}
|
||||||
|
|
||||||
impl Render for Session {
|
impl Render for Session {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let mut buttons = vec![];
|
let (status_text, interrupt_button) = match &self.kernel {
|
||||||
|
Kernel::RunningKernel(kernel) => (
|
||||||
buttons.push(
|
kernel
|
||||||
ButtonLike::new("shutdown")
|
.kernel_info
|
||||||
.child(Label::new("Shutdown"))
|
.as_ref()
|
||||||
.style(ButtonStyle::Subtle)
|
.map(|info| info.language_info.name.clone()),
|
||||||
.on_click(cx.listener(move |session, _, cx| {
|
Some(
|
||||||
session.shutdown(cx);
|
Button::new("interrupt", "Interrupt")
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
let status_text = match &self.kernel {
|
|
||||||
Kernel::RunningKernel(kernel) => {
|
|
||||||
buttons.push(
|
|
||||||
ButtonLike::new("interrupt")
|
|
||||||
.child(Label::new("Interrupt"))
|
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.on_click(cx.listener(move |session, _, cx| {
|
.on_click(cx.listener(move |session, _, cx| {
|
||||||
session.interrupt(cx);
|
session.interrupt(cx);
|
||||||
})),
|
})),
|
||||||
);
|
),
|
||||||
let mut name = self.kernel_specification.name.clone();
|
),
|
||||||
|
Kernel::StartingKernel(_) => (Some("Starting".into()), None),
|
||||||
if let Some(info) = &kernel.kernel_info {
|
Kernel::ErroredLaunch(err) => (Some(format!("Error: {err}")), None),
|
||||||
name.push_str(" (");
|
Kernel::ShuttingDown => (Some("Shutting Down".into()), None),
|
||||||
name.push_str(&info.language_info.name);
|
Kernel::Shutdown => (Some("Shutdown".into()), None),
|
||||||
name.push_str(")");
|
|
||||||
}
|
|
||||||
name
|
|
||||||
}
|
|
||||||
Kernel::StartingKernel(_) => format!("{} (Starting)", self.kernel_specification.name),
|
|
||||||
Kernel::ErroredLaunch(err) => {
|
|
||||||
format!("{} (Error: {})", self.kernel_specification.name, err)
|
|
||||||
}
|
|
||||||
Kernel::ShuttingDown => format!("{} (Shutting Down)", self.kernel_specification.name),
|
|
||||||
Kernel::Shutdown => format!("{} (Shutdown)", self.kernel_specification.name),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return v_flex()
|
KernelListItem::new(self.kernel_specification.clone())
|
||||||
.gap_1()
|
.status_color(match &self.kernel {
|
||||||
.child(
|
Kernel::RunningKernel(kernel) => match kernel.execution_state {
|
||||||
h_flex()
|
ExecutionState::Idle => Color::Success,
|
||||||
.gap_2()
|
ExecutionState::Busy => Color::Modified,
|
||||||
.child(self.kernel.dot())
|
},
|
||||||
.child(Label::new(status_text)),
|
Kernel::StartingKernel(_) => Color::Modified,
|
||||||
|
Kernel::ErroredLaunch(_) => Color::Error,
|
||||||
|
Kernel::ShuttingDown => Color::Modified,
|
||||||
|
Kernel::Shutdown => Color::Disabled,
|
||||||
|
})
|
||||||
|
.child(Label::new(self.kernel_specification.name.clone()))
|
||||||
|
.children(status_text.map(|status_text| Label::new(format!("({status_text})"))))
|
||||||
|
.button(
|
||||||
|
Button::new("shutdown", "Shutdown")
|
||||||
|
.style(ButtonStyle::Subtle)
|
||||||
|
.disabled(self.kernel.is_shutting_down())
|
||||||
|
.on_click(cx.listener(move |session, _, cx| {
|
||||||
|
session.shutdown(cx);
|
||||||
|
})),
|
||||||
)
|
)
|
||||||
.child(h_flex().gap_2().children(buttons));
|
.buttons(interrupt_button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue