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:
parent
9211e699ee
commit
e0245b3f30
9 changed files with 9 additions and 63 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
401
crates/zed/src/zed/quick_action_bar.rs
Normal file
401
crates/zed/src/zed/quick_action_bar.rs
Normal 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()
|
||||
}
|
||||
}
|
65
crates/zed/src/zed/quick_action_bar/markdown_preview.rs
Normal file
65
crates/zed/src/zed/quick_action_bar/markdown_preview.rs
Normal 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())
|
||||
}
|
||||
}
|
455
crates/zed/src/zed/quick_action_bar/repl_menu.rs
Normal file
455
crates/zed/src/zed/quick_action_bar/repl_menu.rs
Normal 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()
|
||||
},
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue