Stick REPL icon in quick action bar (#14064)
REPL Quick Actions <img width="325" alt="image" src="https://github.com/zed-industries/zed/assets/836375/faaf4c8f-ef12-4417-a9dd-158d5beae8ba"> When the Jupyter REPL is enabled and a kernel is available, show the status in the editor bar:  Release Notes: - N/A --------- Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
co-authored by
Nate Butler
parent
9282bf97ae
commit
896b9bda23
@@ -20,6 +20,7 @@ search.workspace = true
|
||||
settings.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
repl.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -20,8 +20,11 @@ use workspace::{
|
||||
item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
};
|
||||
|
||||
mod repl_menu;
|
||||
|
||||
pub struct QuickActionBar {
|
||||
buffer_search_bar: View<BufferSearchBar>,
|
||||
repl_menu: Option<View<ContextMenu>>,
|
||||
toggle_settings_menu: Option<View<ContextMenu>>,
|
||||
toggle_selections_menu: Option<View<ContextMenu>>,
|
||||
active_item: Option<Box<dyn ItemHandle>>,
|
||||
@@ -40,6 +43,7 @@ impl QuickActionBar {
|
||||
buffer_search_bar,
|
||||
toggle_settings_menu: None,
|
||||
toggle_selections_menu: None,
|
||||
repl_menu: None,
|
||||
active_item: None,
|
||||
_inlay_hints_enabled_subscription: None,
|
||||
workspace: workspace.weak_handle(),
|
||||
@@ -290,9 +294,13 @@ impl Render for QuickActionBar {
|
||||
.child(
|
||||
h_flex()
|
||||
.gap(Spacing::Medium.rems(cx))
|
||||
.children(self.render_repl_menu(cx))
|
||||
.children(editor_selections_dropdown)
|
||||
.child(editor_settings_dropdown),
|
||||
)
|
||||
.when_some(self.repl_menu.as_ref(), |el, repl_menu| {
|
||||
el.child(Self::render_menu_overlay(repl_menu))
|
||||
})
|
||||
.when_some(
|
||||
self.toggle_settings_menu.as_ref(),
|
||||
|el, toggle_settings_menu| {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
use gpui::AnyElement;
|
||||
use repl::{
|
||||
ExecutionState, JupyterSettings, Kernel, KernelSpecification, RuntimePanel, Session,
|
||||
SessionSupport,
|
||||
};
|
||||
use ui::{prelude::*, ButtonLike, IconWithIndicator, IntoElement, Tooltip};
|
||||
|
||||
use crate::QuickActionBar;
|
||||
|
||||
const ZED_REPL_DOCUMENTATION: &str = "https://zed.dev/docs/repl";
|
||||
|
||||
impl QuickActionBar {
|
||||
pub fn render_repl_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let workspace = self.workspace.upgrade()?.read(cx);
|
||||
|
||||
let (editor, repl_panel) = if let (Some(editor), Some(repl_panel)) =
|
||||
(self.active_editor(), workspace.panel::<RuntimePanel>(cx))
|
||||
{
|
||||
(editor, repl_panel)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let session = repl_panel.update(cx, |repl_panel, cx| {
|
||||
repl_panel.session(editor.downgrade(), cx)
|
||||
});
|
||||
|
||||
let session = match session {
|
||||
SessionSupport::ActiveSession(session) => session.read(cx),
|
||||
SessionSupport::Inactive(spec) => {
|
||||
return self.render_repl_launch_menu(spec, cx);
|
||||
}
|
||||
SessionSupport::RequiresSetup(language) => {
|
||||
return self.render_repl_setup(&language, cx);
|
||||
}
|
||||
SessionSupport::Unsupported => return None,
|
||||
};
|
||||
|
||||
let kernel_name: SharedString = session.kernel_specification.name.clone().into();
|
||||
let kernel_language: SharedString = session
|
||||
.kernel_specification
|
||||
.kernelspec
|
||||
.language
|
||||
.clone()
|
||||
.into();
|
||||
|
||||
let tooltip = |session: &Session| match &session.kernel {
|
||||
Kernel::RunningKernel(kernel) => match &kernel.execution_state {
|
||||
ExecutionState::Idle => {
|
||||
format!("Run code on {} ({})", kernel_name, kernel_language)
|
||||
}
|
||||
ExecutionState::Busy => format!("Interrupt {} ({})", kernel_name, kernel_language),
|
||||
},
|
||||
Kernel::StartingKernel(_) => format!("{} is starting", kernel_name),
|
||||
Kernel::ErroredLaunch(e) => format!("Error with kernel {}: {}", kernel_name, e),
|
||||
Kernel::ShuttingDown => format!("{} is shutting down", kernel_name),
|
||||
Kernel::Shutdown => "Nothing running".to_string(),
|
||||
};
|
||||
|
||||
let tooltip_text: SharedString = SharedString::from(tooltip(&session).clone());
|
||||
|
||||
let button = ButtonLike::new("toggle_repl_icon")
|
||||
.child(
|
||||
IconWithIndicator::new(Icon::new(IconName::Play), Some(session.kernel.dot()))
|
||||
.indicator_border_color(Some(cx.theme().colors().border)),
|
||||
)
|
||||
.size(ButtonSize::Compact)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text(tooltip_text.clone(), cx))
|
||||
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
|
||||
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
|
||||
.into_any_element();
|
||||
|
||||
Some(button)
|
||||
}
|
||||
|
||||
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(
|
||||
IconButton::new("toggle_repl_icon", IconName::Play)
|
||||
.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_repl_setup(
|
||||
&self,
|
||||
language: &str,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) -> Option<AnyElement> {
|
||||
let tooltip: SharedString = SharedString::from(format!("Setup Zed REPL for {}", language));
|
||||
Some(
|
||||
IconButton::new("toggle_repl_icon", IconName::Play)
|
||||
.size(ButtonSize::Compact)
|
||||
.icon_color(Color::Muted)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
|
||||
.on_click(|_, cx| cx.open_url(ZED_REPL_DOCUMENTATION))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ pub enum Kernel {
|
||||
}
|
||||
|
||||
impl Kernel {
|
||||
pub fn dot(&mut self) -> Indicator {
|
||||
pub fn dot(&self) -> Indicator {
|
||||
match self {
|
||||
Kernel::RunningKernel(kernel) => match kernel.execution_state {
|
||||
ExecutionState::Idle => Indicator::dot().color(Color::Success),
|
||||
|
||||
@@ -11,7 +11,11 @@ mod session;
|
||||
mod stdio;
|
||||
|
||||
pub use jupyter_settings::JupyterSettings;
|
||||
pub use runtime_panel::RuntimePanel;
|
||||
pub use kernels::{Kernel, KernelSpecification};
|
||||
pub use runtime_panel::Run;
|
||||
pub use runtime_panel::{RuntimePanel, SessionSupport};
|
||||
pub use runtimelib::ExecutionState;
|
||||
pub use session::Session;
|
||||
|
||||
fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
|
||||
struct ZedDispatcher {
|
||||
|
||||
@@ -241,6 +241,17 @@ impl RuntimePanel {
|
||||
Some((selected_text, language_name, anchor_range))
|
||||
}
|
||||
|
||||
pub fn language(
|
||||
&self,
|
||||
editor: WeakView<Editor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Arc<str>> {
|
||||
match self.snippet(editor, cx) {
|
||||
Some((_, language, _)) => Some(language),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_kernelspecs(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
|
||||
let kernel_specifications = kernel_specifications(self.fs.clone());
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
@@ -336,6 +347,50 @@ impl RuntimePanel {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SessionSupport {
|
||||
ActiveSession(View<Session>),
|
||||
Inactive(KernelSpecification),
|
||||
RequiresSetup(String),
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
impl RuntimePanel {
|
||||
pub fn session(
|
||||
&mut self,
|
||||
editor: WeakView<Editor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> SessionSupport {
|
||||
let entity_id = editor.entity_id();
|
||||
let session = self.sessions.get(&entity_id).cloned();
|
||||
|
||||
match session {
|
||||
Some(session) => SessionSupport::ActiveSession(session),
|
||||
None => {
|
||||
let language = self.language(editor, cx);
|
||||
let language = match language {
|
||||
Some(language) => language,
|
||||
None => return SessionSupport::Unsupported,
|
||||
};
|
||||
// Check for kernelspec
|
||||
let kernelspec = self.kernelspec(&language, cx);
|
||||
|
||||
match kernelspec {
|
||||
Some(kernelspec) => SessionSupport::Inactive(kernelspec),
|
||||
None => {
|
||||
let language: String = language.to_lowercase();
|
||||
// If no kernelspec but language is one of typescript, python, r, or julia
|
||||
// then we return RequiresSetup
|
||||
match language.as_str() {
|
||||
"typescript" | "python" => SessionSupport::RequiresSetup(language),
|
||||
_ => SessionSupport::Unsupported,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Panel for RuntimePanel {
|
||||
fn persistent_name() -> &'static str {
|
||||
"RuntimePanel"
|
||||
|
||||
@@ -22,11 +22,11 @@ use theme::{ActiveTheme, ThemeSettings};
|
||||
use ui::{h_flex, prelude::*, v_flex, ButtonLike, ButtonStyle, Label};
|
||||
|
||||
pub struct Session {
|
||||
editor: WeakView<Editor>,
|
||||
kernel: Kernel,
|
||||
pub editor: WeakView<Editor>,
|
||||
pub kernel: Kernel,
|
||||
blocks: HashMap<String, EditorBlock>,
|
||||
messaging_task: Task<()>,
|
||||
kernel_specification: KernelSpecification,
|
||||
pub messaging_task: Task<()>,
|
||||
pub kernel_specification: KernelSpecification,
|
||||
}
|
||||
|
||||
struct EditorBlock {
|
||||
@@ -310,7 +310,7 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
fn interrupt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub fn interrupt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
match &mut self.kernel {
|
||||
Kernel::RunningKernel(_kernel) => {
|
||||
self.send(InterruptRequest {}.into(), cx).ok();
|
||||
@@ -322,7 +322,7 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
fn shutdown(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub fn shutdown(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let kernel = std::mem::replace(&mut self.kernel, Kernel::ShuttingDown);
|
||||
|
||||
match kernel {
|
||||
|
||||
@@ -160,11 +160,11 @@ pub enum IconName {
|
||||
Font,
|
||||
FontSize,
|
||||
FontWeight,
|
||||
Github,
|
||||
GenericMinimize,
|
||||
GenericMaximize,
|
||||
GenericClose,
|
||||
GenericMaximize,
|
||||
GenericMinimize,
|
||||
GenericRestore,
|
||||
Github,
|
||||
Hash,
|
||||
HistoryRerun,
|
||||
Indicator,
|
||||
@@ -194,6 +194,10 @@ pub enum IconName {
|
||||
PullRequest,
|
||||
Quote,
|
||||
Regex,
|
||||
ReplPlay,
|
||||
ReplOff,
|
||||
ReplPause,
|
||||
ReplNeutral,
|
||||
Replace,
|
||||
ReplaceAll,
|
||||
ReplaceNext,
|
||||
@@ -231,12 +235,12 @@ pub enum IconName {
|
||||
Trash,
|
||||
TriangleRight,
|
||||
Update,
|
||||
Visible,
|
||||
WholeWord,
|
||||
XCircle,
|
||||
ZedAssistant,
|
||||
ZedAssistantFilled,
|
||||
ZedXCopilot,
|
||||
Visible,
|
||||
}
|
||||
|
||||
impl IconName {
|
||||
@@ -308,11 +312,11 @@ impl IconName {
|
||||
IconName::Font => "icons/font.svg",
|
||||
IconName::FontSize => "icons/font_size.svg",
|
||||
IconName::FontWeight => "icons/font_weight.svg",
|
||||
IconName::Github => "icons/github.svg",
|
||||
IconName::GenericMinimize => "icons/generic_minimize.svg",
|
||||
IconName::GenericMaximize => "icons/generic_maximize.svg",
|
||||
IconName::GenericClose => "icons/generic_close.svg",
|
||||
IconName::GenericMaximize => "icons/generic_maximize.svg",
|
||||
IconName::GenericMinimize => "icons/generic_minimize.svg",
|
||||
IconName::GenericRestore => "icons/generic_restore.svg",
|
||||
IconName::Github => "icons/github.svg",
|
||||
IconName::Hash => "icons/hash.svg",
|
||||
IconName::HistoryRerun => "icons/history_rerun.svg",
|
||||
IconName::Indicator => "icons/indicator.svg",
|
||||
@@ -342,6 +346,10 @@ impl IconName {
|
||||
IconName::PullRequest => "icons/pull_request.svg",
|
||||
IconName::Quote => "icons/quote.svg",
|
||||
IconName::Regex => "icons/regex.svg",
|
||||
IconName::ReplPlay => "icons/repl_play.svg",
|
||||
IconName::ReplPause => "icons/repl_pause.svg",
|
||||
IconName::ReplNeutral => "icons/repl_neutral.svg",
|
||||
IconName::ReplOff => "icons/repl_off.svg",
|
||||
IconName::Replace => "icons/replace.svg",
|
||||
IconName::ReplaceAll => "icons/replace_all.svg",
|
||||
IconName::ReplaceNext => "icons/replace_next.svg",
|
||||
@@ -379,12 +387,12 @@ impl IconName {
|
||||
IconName::Trash => "icons/trash.svg",
|
||||
IconName::TriangleRight => "icons/triangle_right.svg",
|
||||
IconName::Update => "icons/update.svg",
|
||||
IconName::Visible => "icons/visible.svg",
|
||||
IconName::WholeWord => "icons/word_search.svg",
|
||||
IconName::XCircle => "icons/error.svg",
|
||||
IconName::ZedAssistant => "icons/zed_assistant.svg",
|
||||
IconName::ZedAssistantFilled => "icons/zed_assistant_filled.svg",
|
||||
IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
|
||||
IconName::Visible => "icons/visible.svg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user