Merge quick_action_bar into zed (#21026)

This PR merges the `quick_action_bar` crate into the `zed` crate.

We weren't really gaining anything by having it be a separate crate, and
it was introducing an additional step in the dependency graph that was
getting in the way.

It's only ~850 LOC, so the impact on the compilation speed of the `zed`
crate itself is negligible.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-11-21 18:33:11 -05:00 committed by GitHub
parent 9211e699ee
commit e0245b3f30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 9 additions and 63 deletions

View file

@ -78,12 +78,12 @@ outline.workspace = true
outline_panel.workspace = true
parking_lot.workspace = true
paths.workspace = true
picker.workspace = true
profiling.workspace = true
project.workspace = true
project_panel.workspace = true
project_symbols.workspace = true
proto.workspace = true
quick_action_bar.workspace = true
recent_projects.workspace = true
release_channel.workspace = true
remote.workspace = true

View file

@ -6,6 +6,7 @@ pub(crate) mod linux_prompts;
#[cfg(target_os = "macos")]
pub(crate) mod mac_only_instance;
mod open_listener;
mod quick_action_bar;
#[cfg(target_os = "windows")]
pub(crate) mod windows_only_instance;

View file

@ -0,0 +1,401 @@
mod markdown_preview;
mod repl_menu;
use assistant::assistant_settings::AssistantSettings;
use assistant::AssistantPanel;
use editor::actions::{
AddSelectionAbove, AddSelectionBelow, DuplicateLineDown, GoToDiagnostic, GoToHunk,
GoToPrevDiagnostic, GoToPrevHunk, MoveLineDown, MoveLineUp, SelectAll, SelectLargerSyntaxNode,
SelectNext, SelectSmallerSyntaxNode, ToggleGoToLine, ToggleOutline,
};
use editor::{Editor, EditorSettings};
use gpui::{
Action, AnchorCorner, ClickEvent, ElementId, EventEmitter, FocusHandle, FocusableView,
InteractiveElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView,
};
use search::{buffer_search, BufferSearchBar};
use settings::{Settings, SettingsStore};
use ui::{
prelude::*, ButtonStyle, ContextMenu, IconButton, IconButtonShape, IconName, IconSize,
PopoverMenu, PopoverMenuHandle, Tooltip,
};
use workspace::{
item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
};
use zed_actions::InlineAssist;
pub struct QuickActionBar {
_inlay_hints_enabled_subscription: Option<Subscription>,
active_item: Option<Box<dyn ItemHandle>>,
buffer_search_bar: View<BufferSearchBar>,
show: bool,
toggle_selections_handle: PopoverMenuHandle<ContextMenu>,
toggle_settings_handle: PopoverMenuHandle<ContextMenu>,
workspace: WeakView<Workspace>,
}
impl QuickActionBar {
pub fn new(
buffer_search_bar: View<BufferSearchBar>,
workspace: &Workspace,
cx: &mut ViewContext<Self>,
) -> Self {
let mut this = Self {
_inlay_hints_enabled_subscription: None,
active_item: None,
buffer_search_bar,
show: true,
toggle_selections_handle: Default::default(),
toggle_settings_handle: Default::default(),
workspace: workspace.weak_handle(),
};
this.apply_settings(cx);
cx.observe_global::<SettingsStore>(|this, cx| this.apply_settings(cx))
.detach();
this
}
fn active_editor(&self) -> Option<View<Editor>> {
self.active_item
.as_ref()
.and_then(|item| item.downcast::<Editor>())
}
fn apply_settings(&mut self, cx: &mut ViewContext<Self>) {
let new_show = EditorSettings::get_global(cx).toolbar.quick_actions;
if new_show != self.show {
self.show = new_show;
cx.emit(ToolbarItemEvent::ChangeLocation(
self.get_toolbar_item_location(),
));
}
}
fn get_toolbar_item_location(&self) -> ToolbarItemLocation {
if self.show && self.active_editor().is_some() {
ToolbarItemLocation::PrimaryRight
} else {
ToolbarItemLocation::Hidden
}
}
}
impl Render for QuickActionBar {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let Some(editor) = self.active_editor() else {
return div().id("empty quick action bar");
};
let (
selection_menu_enabled,
inlay_hints_enabled,
supports_inlay_hints,
git_blame_inline_enabled,
auto_signature_help_enabled,
) = {
let editor = editor.read(cx);
let selection_menu_enabled = editor.selection_menu_enabled(cx);
let inlay_hints_enabled = editor.inlay_hints_enabled();
let supports_inlay_hints = editor.supports_inlay_hints(cx);
let git_blame_inline_enabled = editor.git_blame_inline_enabled();
let auto_signature_help_enabled = editor.auto_signature_help_enabled(cx);
(
selection_menu_enabled,
inlay_hints_enabled,
supports_inlay_hints,
git_blame_inline_enabled,
auto_signature_help_enabled,
)
};
let focus_handle = editor.read(cx).focus_handle(cx);
let search_button = editor.is_singleton(cx).then(|| {
QuickActionBarButton::new(
"toggle buffer search",
IconName::MagnifyingGlass,
!self.buffer_search_bar.read(cx).is_dismissed(),
Box::new(buffer_search::Deploy::find()),
focus_handle.clone(),
"Buffer Search",
{
let buffer_search_bar = self.buffer_search_bar.clone();
move |_, cx| {
buffer_search_bar.update(cx, |search_bar, cx| {
search_bar.toggle(&buffer_search::Deploy::find(), cx)
});
}
},
)
});
let assistant_button = QuickActionBarButton::new(
"toggle inline assistant",
IconName::ZedAssistant,
false,
Box::new(InlineAssist::default()),
focus_handle.clone(),
"Inline Assist",
{
let workspace = self.workspace.clone();
move |_, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
AssistantPanel::inline_assist(workspace, &InlineAssist::default(), cx);
});
}
}
},
);
let editor_selections_dropdown = selection_menu_enabled.then(|| {
let focus = editor.focus_handle(cx);
PopoverMenu::new("editor-selections-dropdown")
.trigger(
IconButton::new("toggle_editor_selections_icon", IconName::CursorIBeam)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(self.toggle_selections_handle.is_deployed())
.when(!self.toggle_selections_handle.is_deployed(), |this| {
this.tooltip(|cx| Tooltip::text("Selection Controls", cx))
}),
)
.with_handle(self.toggle_selections_handle.clone())
.anchor(AnchorCorner::TopRight)
.menu(move |cx| {
let focus = focus.clone();
let menu = ContextMenu::build(cx, move |menu, _| {
menu.context(focus.clone())
.action("Select All", Box::new(SelectAll))
.action(
"Select Next Occurrence",
Box::new(SelectNext {
replace_newest: false,
}),
)
.action("Expand Selection", Box::new(SelectLargerSyntaxNode))
.action("Shrink Selection", Box::new(SelectSmallerSyntaxNode))
.action("Add Cursor Above", Box::new(AddSelectionAbove))
.action("Add Cursor Below", Box::new(AddSelectionBelow))
.separator()
.action("Go to Symbol", Box::new(ToggleOutline))
.action("Go to Line/Column", Box::new(ToggleGoToLine))
.separator()
.action("Next Problem", Box::new(GoToDiagnostic))
.action("Previous Problem", Box::new(GoToPrevDiagnostic))
.separator()
.action("Next Hunk", Box::new(GoToHunk))
.action("Previous Hunk", Box::new(GoToPrevHunk))
.separator()
.action("Move Line Up", Box::new(MoveLineUp))
.action("Move Line Down", Box::new(MoveLineDown))
.action("Duplicate Selection", Box::new(DuplicateLineDown))
});
Some(menu)
})
});
let editor = editor.downgrade();
let editor_settings_dropdown = PopoverMenu::new("editor-settings")
.trigger(
IconButton::new("toggle_editor_settings_icon", IconName::Sliders)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(self.toggle_settings_handle.is_deployed())
.when(!self.toggle_settings_handle.is_deployed(), |this| {
this.tooltip(|cx| Tooltip::text("Editor Controls", cx))
}),
)
.anchor(AnchorCorner::TopRight)
.with_handle(self.toggle_settings_handle.clone())
.menu(move |cx| {
let menu = ContextMenu::build(cx, |mut menu, _| {
if supports_inlay_hints {
menu = menu.toggleable_entry(
"Inlay Hints",
inlay_hints_enabled,
IconPosition::Start,
Some(editor::actions::ToggleInlayHints.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor
.update(cx, |editor, cx| {
editor.toggle_inlay_hints(
&editor::actions::ToggleInlayHints,
cx,
);
})
.ok();
}
},
);
}
menu = menu.toggleable_entry(
"Inline Git Blame",
git_blame_inline_enabled,
IconPosition::Start,
Some(editor::actions::ToggleGitBlameInline.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor
.update(cx, |editor, cx| {
editor.toggle_git_blame_inline(
&editor::actions::ToggleGitBlameInline,
cx,
)
})
.ok();
}
},
);
menu = menu.toggleable_entry(
"Selection Menu",
selection_menu_enabled,
IconPosition::Start,
Some(editor::actions::ToggleSelectionMenu.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor
.update(cx, |editor, cx| {
editor.toggle_selection_menu(
&editor::actions::ToggleSelectionMenu,
cx,
)
})
.ok();
}
},
);
menu = menu.toggleable_entry(
"Auto Signature Help",
auto_signature_help_enabled,
IconPosition::Start,
Some(editor::actions::ToggleAutoSignatureHelp.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor
.update(cx, |editor, cx| {
editor.toggle_auto_signature_help_menu(
&editor::actions::ToggleAutoSignatureHelp,
cx,
);
})
.ok();
}
},
);
menu
});
Some(menu)
});
h_flex()
.id("quick action bar")
.gap(DynamicSpacing::Base06.rems(cx))
.children(self.render_repl_menu(cx))
.children(self.render_toggle_markdown_preview(self.workspace.clone(), cx))
.children(search_button)
.when(
AssistantSettings::get_global(cx).enabled
&& AssistantSettings::get_global(cx).button,
|bar| bar.child(assistant_button),
)
.children(editor_selections_dropdown)
.child(editor_settings_dropdown)
}
}
impl EventEmitter<ToolbarItemEvent> for QuickActionBar {}
#[derive(IntoElement)]
struct QuickActionBarButton {
id: ElementId,
icon: IconName,
toggled: bool,
action: Box<dyn Action>,
focus_handle: FocusHandle,
tooltip: SharedString,
on_click: Box<dyn Fn(&ClickEvent, &mut WindowContext)>,
}
impl QuickActionBarButton {
fn new(
id: impl Into<ElementId>,
icon: IconName,
toggled: bool,
action: Box<dyn Action>,
focus_handle: FocusHandle,
tooltip: impl Into<SharedString>,
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
) -> Self {
Self {
id: id.into(),
icon,
toggled,
action,
focus_handle,
tooltip: tooltip.into(),
on_click: Box::new(on_click),
}
}
}
impl RenderOnce for QuickActionBarButton {
fn render(self, _: &mut WindowContext) -> impl IntoElement {
let tooltip = self.tooltip.clone();
let action = self.action.boxed_clone();
IconButton::new(self.id.clone(), self.icon)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(self.toggled)
.tooltip(move |cx| {
Tooltip::for_action_in(tooltip.clone(), &*action, &self.focus_handle, cx)
})
.on_click(move |event, cx| (self.on_click)(event, cx))
}
}
impl ToolbarItemView for QuickActionBar {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
) -> ToolbarItemLocation {
self.active_item = active_pane_item.map(ItemHandle::boxed_clone);
if let Some(active_item) = active_pane_item {
self._inlay_hints_enabled_subscription.take();
if let Some(editor) = active_item.downcast::<Editor>() {
let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
self._inlay_hints_enabled_subscription =
Some(cx.observe(&editor, move |_, editor, cx| {
let editor = editor.read(cx);
let new_inlay_hints_enabled = editor.inlay_hints_enabled();
let new_supports_inlay_hints = editor.supports_inlay_hints(cx);
let should_notify = inlay_hints_enabled != new_inlay_hints_enabled
|| supports_inlay_hints != new_supports_inlay_hints;
inlay_hints_enabled = new_inlay_hints_enabled;
supports_inlay_hints = new_supports_inlay_hints;
if should_notify {
cx.notify()
}
}));
}
}
self.get_toolbar_item_location()
}
}

View file

@ -0,0 +1,65 @@
use gpui::{AnyElement, Modifiers, WeakView};
use markdown_preview::{
markdown_preview_view::MarkdownPreviewView, OpenPreview, OpenPreviewToTheSide,
};
use ui::{prelude::*, text_for_keystroke, IconButtonShape, Tooltip};
use workspace::Workspace;
use super::QuickActionBar;
impl QuickActionBar {
pub fn render_toggle_markdown_preview(
&self,
workspace: WeakView<Workspace>,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement> {
let mut active_editor_is_markdown = false;
if let Some(workspace) = self.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
active_editor_is_markdown =
MarkdownPreviewView::resolve_active_item_as_markdown_editor(workspace, cx)
.is_some();
});
}
if !active_editor_is_markdown {
return None;
}
let alt_click = gpui::Keystroke {
key: "click".into(),
modifiers: Modifiers::alt(),
..Default::default()
};
let button = IconButton::new("toggle-markdown-preview", IconName::Eye)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| {
Tooltip::with_meta(
"Preview Markdown",
Some(&markdown_preview::OpenPreview),
format!(
"{} to open in a split",
text_for_keystroke(&alt_click, PlatformStyle::platform())
),
cx,
)
})
.on_click(move |_, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |_, cx| {
if cx.modifiers().alt {
cx.dispatch_action(Box::new(OpenPreviewToTheSide));
} else {
cx.dispatch_action(Box::new(OpenPreview));
}
});
}
});
Some(button.into_any_element())
}
}

View file

@ -0,0 +1,455 @@
use std::time::Duration;
use gpui::ElementId;
use gpui::{percentage, Animation, AnimationExt, AnyElement, Transformation, View};
use picker::Picker;
use repl::{
components::{KernelPickerDelegate, KernelSelector},
worktree_id_for_editor, ExecutionState, JupyterSettings, Kernel, KernelSpecification,
KernelStatus, Session, SessionSupport,
};
use ui::{
prelude::*, ButtonLike, ContextMenu, IconWithIndicator, Indicator, IntoElement, PopoverMenu,
PopoverMenuHandle, Tooltip,
};
use util::ResultExt;
use super::QuickActionBar;
const ZED_REPL_DOCUMENTATION: &str = "https://zed.dev/docs/repl";
struct ReplMenuState {
tooltip: SharedString,
icon: IconName,
icon_color: Color,
icon_is_animating: bool,
popover_disabled: bool,
indicator: Option<Indicator>,
status: KernelStatus,
kernel_name: SharedString,
kernel_language: SharedString,
}
impl QuickActionBar {
pub fn render_repl_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
if !JupyterSettings::enabled(cx) {
return None;
}
let editor = self.active_editor()?;
let is_local_project = editor
.read(cx)
.workspace()
.map(|workspace| workspace.read(cx).project().read(cx).is_local())
.unwrap_or(false);
if !is_local_project {
return None;
}
let has_nonempty_selection = {
editor.update(cx, |this, cx| {
this.selections
.count()
.ne(&0)
.then(|| {
let latest = this.selections.newest_display(cx);
!latest.is_empty()
})
.unwrap_or_default()
})
};
let session = repl::session(editor.downgrade(), cx);
let session = match session {
SessionSupport::ActiveSession(session) => session,
SessionSupport::Inactive(spec) => {
return self.render_repl_launch_menu(spec, cx);
}
SessionSupport::RequiresSetup(language) => {
return self.render_repl_setup(&language.0, cx);
}
SessionSupport::Unsupported => return None,
};
let menu_state = session_state(session.clone(), cx);
let id = "repl-menu".to_string();
let element_id = |suffix| ElementId::Name(format!("{}-{}", id, suffix).into());
let editor = editor.downgrade();
let dropdown_menu = PopoverMenu::new(element_id("menu"))
.menu(move |cx| {
let editor = editor.clone();
let session = session.clone();
ContextMenu::build(cx, move |menu, cx| {
let menu_state = session_state(session, cx);
let status = menu_state.status;
let editor = editor.clone();
menu.map(|menu| {
if status.is_connected() {
let status = status.clone();
menu.custom_row(move |_cx| {
h_flex()
.child(
Label::new(format!(
"kernel: {} ({})",
menu_state.kernel_name.clone(),
menu_state.kernel_language.clone()
))
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any_element()
})
.custom_row(move |_cx| {
h_flex()
.child(
Label::new(status.clone().to_string())
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any_element()
})
} else {
let status = status.clone();
menu.custom_row(move |_cx| {
h_flex()
.child(
Label::new(format!("{}...", status.clone().to_string()))
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any_element()
})
}
})
.separator()
.custom_entry(
move |_cx| {
Label::new(if has_nonempty_selection {
"Run Selection"
} else {
"Run Line"
})
.into_any_element()
},
{
let editor = editor.clone();
move |cx| {
repl::run(editor.clone(), true, cx).log_err();
}
},
)
.custom_entry(
move |_cx| {
Label::new("Interrupt")
.size(LabelSize::Small)
.color(Color::Error)
.into_any_element()
},
{
let editor = editor.clone();
move |cx| {
repl::interrupt(editor.clone(), cx);
}
},
)
.custom_entry(
move |_cx| {
Label::new("Clear Outputs")
.size(LabelSize::Small)
.color(Color::Muted)
.into_any_element()
},
{
let editor = editor.clone();
move |cx| {
repl::clear_outputs(editor.clone(), cx);
}
},
)
.separator()
.custom_entry(
move |_cx| {
Label::new("Shut Down Kernel")
.size(LabelSize::Small)
.color(Color::Error)
.into_any_element()
},
{
let editor = editor.clone();
move |cx| {
repl::shutdown(editor.clone(), cx);
}
},
)
.custom_entry(
move |_cx| {
Label::new("Restart Kernel")
.size(LabelSize::Small)
.color(Color::Error)
.into_any_element()
},
{
let editor = editor.clone();
move |cx| {
repl::restart(editor.clone(), cx);
}
},
)
.separator()
.action("View Sessions", Box::new(repl::Sessions))
// TODO: Add shut down all kernels action
// .action("Shut Down all Kernels", Box::new(gpui::NoAction))
})
.into()
})
.trigger(
ButtonLike::new_rounded_right(element_id("dropdown"))
.child(
Icon::new(IconName::ChevronDownSmall)
.size(IconSize::XSmall)
.color(Color::Muted),
)
.tooltip(move |cx| Tooltip::text("REPL Menu", cx))
.width(rems(1.).into())
.disabled(menu_state.popover_disabled),
);
let button = ButtonLike::new_rounded_left("toggle_repl_icon")
.child(if menu_state.icon_is_animating {
Icon::new(menu_state.icon)
.color(menu_state.icon_color)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(5)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
)
.into_any_element()
} else {
IconWithIndicator::new(
Icon::new(IconName::ReplNeutral).color(menu_state.icon_color),
menu_state.indicator,
)
.indicator_border_color(Some(cx.theme().colors().toolbar_background))
.into_any_element()
})
.size(ButtonSize::Compact)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text(menu_state.tooltip.clone(), cx))
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
.into_any_element();
Some(
h_flex()
.child(self.render_kernel_selector(cx))
.child(button)
.child(dropdown_menu)
.into_any_element(),
)
}
pub fn render_repl_launch_menu(
&self,
kernel_specification: KernelSpecification,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement> {
let tooltip: SharedString =
SharedString::from(format!("Start REPL for {}", kernel_specification.name()));
Some(
h_flex()
.child(self.render_kernel_selector(cx))
.child(
IconButton::new("toggle_repl_icon", IconName::ReplNeutral)
.size(ButtonSize::Compact)
.icon_color(Color::Muted)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {}))),
)
.into_any_element(),
)
}
pub fn render_kernel_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let editor = if let Some(editor) = self.active_editor() {
editor
} else {
return div().into_any_element();
};
let Some(worktree_id) = worktree_id_for_editor(editor.downgrade(), cx) else {
return div().into_any_element();
};
let session = repl::session(editor.downgrade(), cx);
let current_kernelspec = match session {
SessionSupport::ActiveSession(view) => Some(view.read(cx).kernel_specification.clone()),
SessionSupport::Inactive(kernel_specification) => Some(kernel_specification),
SessionSupport::RequiresSetup(_language_name) => None,
SessionSupport::Unsupported => None,
};
let current_kernel_name = current_kernelspec.as_ref().map(|spec| spec.name());
let menu_handle: PopoverMenuHandle<Picker<KernelPickerDelegate>> =
PopoverMenuHandle::default();
KernelSelector::new(
{
Box::new(move |kernelspec, cx| {
repl::assign_kernelspec(kernelspec, editor.downgrade(), cx).ok();
})
},
worktree_id,
ButtonLike::new("kernel-selector")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.w_full()
.gap_0p5()
.child(
div()
.overflow_x_hidden()
.flex_grow()
.whitespace_nowrap()
.child(
Label::new(if let Some(name) = current_kernel_name {
name
} else {
SharedString::from("Select Kernel")
})
.size(LabelSize::Small)
.color(if current_kernelspec.is_some() {
Color::Default
} else {
Color::Placeholder
})
.into_any_element(),
),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
)
.tooltip(move |cx| Tooltip::text("Select Kernel", cx)),
)
.with_handle(menu_handle.clone())
.into_any_element()
}
pub fn render_repl_setup(
&self,
language: &str,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement> {
let tooltip: SharedString = SharedString::from(format!("Setup Zed REPL for {}", language));
Some(
h_flex()
.child(self.render_kernel_selector(cx))
.child(
IconButton::new("toggle_repl_icon", IconName::ReplNeutral)
.size(ButtonSize::Compact)
.icon_color(Color::Muted)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
.on_click(|_, cx| {
cx.open_url(&format!("{}#installation", ZED_REPL_DOCUMENTATION))
}),
)
.into_any_element(),
)
}
}
fn session_state(session: View<Session>, cx: &WindowContext) -> ReplMenuState {
let session = session.read(cx);
let kernel_name = session.kernel_specification.name();
let kernel_language: SharedString = session.kernel_specification.language();
let fill_fields = || {
ReplMenuState {
tooltip: "Nothing running".into(),
icon: IconName::ReplNeutral,
icon_color: Color::Default,
icon_is_animating: false,
popover_disabled: false,
indicator: None,
kernel_name: kernel_name.clone(),
kernel_language: kernel_language.clone(),
// todo!(): Technically not shutdown, but indeterminate
status: KernelStatus::Shutdown,
// current_delta: Duration::default(),
}
};
match &session.kernel {
Kernel::Restarting => ReplMenuState {
tooltip: format!("Restarting {}", kernel_name).into(),
icon_is_animating: true,
popover_disabled: true,
icon_color: Color::Muted,
indicator: Some(Indicator::dot().color(Color::Muted)),
status: session.kernel.status(),
..fill_fields()
},
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)),
status: session.kernel.status(),
..fill_fields()
},
ExecutionState::Busy => ReplMenuState {
tooltip: format!("Interrupt {} ({})", kernel_name, kernel_language).into(),
icon_is_animating: true,
popover_disabled: false,
indicator: None,
status: session.kernel.status(),
..fill_fields()
},
},
Kernel::StartingKernel(_) => ReplMenuState {
tooltip: format!("{} is starting", kernel_name).into(),
icon_is_animating: true,
popover_disabled: true,
icon_color: Color::Muted,
indicator: Some(Indicator::dot().color(Color::Muted)),
status: session.kernel.status(),
..fill_fields()
},
Kernel::ErroredLaunch(e) => ReplMenuState {
tooltip: format!("Error with kernel {}: {}", kernel_name, e).into(),
popover_disabled: false,
indicator: Some(Indicator::dot().color(Color::Error)),
status: session.kernel.status(),
..fill_fields()
},
Kernel::ShuttingDown => ReplMenuState {
tooltip: format!("{} is shutting down", kernel_name).into(),
popover_disabled: true,
icon_color: Color::Muted,
indicator: Some(Indicator::dot().color(Color::Muted)),
status: session.kernel.status(),
..fill_fields()
},
Kernel::Shutdown => ReplMenuState {
tooltip: "Nothing running".into(),
icon: IconName::ReplNeutral,
icon_color: Color::Default,
icon_is_animating: false,
popover_disabled: false,
indicator: None,
status: KernelStatus::Shutdown,
..fill_fields()
},
}
}