repl_sessions_ui.rs

  1use editor::Editor;
  2use gpui::{
  3    actions, prelude::*, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription, View,
  4};
  5use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding};
  6use util::ResultExt as _;
  7use workspace::item::ItemEvent;
  8use workspace::WorkspaceId;
  9use workspace::{item::Item, Workspace};
 10
 11use crate::jupyter_settings::JupyterSettings;
 12use crate::repl_store::ReplStore;
 13
 14actions!(
 15    repl,
 16    [
 17        Run,
 18        ClearOutputs,
 19        Sessions,
 20        Interrupt,
 21        Shutdown,
 22        RefreshKernelspecs
 23    ]
 24);
 25
 26pub fn init(cx: &mut AppContext) {
 27    cx.observe_new_views(
 28        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
 29            workspace.register_action(|workspace, _: &Sessions, cx| {
 30                let existing = workspace
 31                    .active_pane()
 32                    .read(cx)
 33                    .items()
 34                    .find_map(|item| item.downcast::<ReplSessionsPage>());
 35
 36                if let Some(existing) = existing {
 37                    workspace.activate_item(&existing, true, true, cx);
 38                } else {
 39                    let extensions_page = ReplSessionsPage::new(cx);
 40                    workspace.add_item_to_active_pane(Box::new(extensions_page), None, true, cx)
 41                }
 42            });
 43
 44            workspace.register_action(|_workspace, _: &RefreshKernelspecs, cx| {
 45                let store = ReplStore::global(cx);
 46                store.update(cx, |store, cx| {
 47                    store.refresh_kernelspecs(cx).detach();
 48                });
 49            });
 50        },
 51    )
 52    .detach();
 53
 54    cx.observe_new_views(move |editor: &mut Editor, cx: &mut ViewContext<Editor>| {
 55        if !editor.use_modal_editing() || !editor.buffer().read(cx).is_singleton() {
 56            return;
 57        }
 58
 59        let editor_handle = cx.view().downgrade();
 60
 61        editor
 62            .register_action({
 63                let editor_handle = editor_handle.clone();
 64                move |_: &Run, cx| {
 65                    if !JupyterSettings::enabled(cx) {
 66                        return;
 67                    }
 68
 69                    crate::run(editor_handle.clone(), cx).log_err();
 70                }
 71            })
 72            .detach();
 73
 74        editor
 75            .register_action({
 76                let editor_handle = editor_handle.clone();
 77                move |_: &ClearOutputs, cx| {
 78                    if !JupyterSettings::enabled(cx) {
 79                        return;
 80                    }
 81
 82                    crate::clear_outputs(editor_handle.clone(), cx);
 83                }
 84            })
 85            .detach();
 86
 87        editor
 88            .register_action({
 89                let editor_handle = editor_handle.clone();
 90                move |_: &Interrupt, cx| {
 91                    if !JupyterSettings::enabled(cx) {
 92                        return;
 93                    }
 94
 95                    crate::interrupt(editor_handle.clone(), cx);
 96                }
 97            })
 98            .detach();
 99
100        editor
101            .register_action({
102                let editor_handle = editor_handle.clone();
103                move |_: &Shutdown, cx| {
104                    if !JupyterSettings::enabled(cx) {
105                        return;
106                    }
107
108                    crate::shutdown(editor_handle.clone(), cx);
109                }
110            })
111            .detach();
112    })
113    .detach();
114}
115
116pub struct ReplSessionsPage {
117    focus_handle: FocusHandle,
118    _subscriptions: Vec<Subscription>,
119}
120
121impl ReplSessionsPage {
122    pub fn new(cx: &mut ViewContext<Workspace>) -> View<Self> {
123        cx.new_view(|cx: &mut ViewContext<Self>| {
124            let focus_handle = cx.focus_handle();
125
126            let subscriptions = vec![
127                cx.on_focus_in(&focus_handle, |_this, cx| cx.notify()),
128                cx.on_focus_out(&focus_handle, |_this, _event, cx| cx.notify()),
129            ];
130
131            Self {
132                focus_handle,
133                _subscriptions: subscriptions,
134            }
135        })
136    }
137}
138
139impl EventEmitter<ItemEvent> for ReplSessionsPage {}
140
141impl FocusableView for ReplSessionsPage {
142    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
143        self.focus_handle.clone()
144    }
145}
146
147impl Item for ReplSessionsPage {
148    type Event = ItemEvent;
149
150    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
151        Some("REPL Sessions".into())
152    }
153
154    fn telemetry_event_text(&self) -> Option<&'static str> {
155        Some("repl sessions")
156    }
157
158    fn show_toolbar(&self) -> bool {
159        false
160    }
161
162    fn clone_on_split(
163        &self,
164        _workspace_id: Option<WorkspaceId>,
165        _: &mut ViewContext<Self>,
166    ) -> Option<View<Self>> {
167        None
168    }
169
170    fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
171        f(*event)
172    }
173}
174
175impl Render for ReplSessionsPage {
176    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
177        let store = ReplStore::global(cx);
178
179        let (kernel_specifications, sessions) = store.update(cx, |store, _cx| {
180            (
181                store.kernel_specifications().cloned().collect::<Vec<_>>(),
182                store.sessions().cloned().collect::<Vec<_>>(),
183            )
184        });
185
186        // When there are no kernel specifications, show a link to the Zed docs explaining how to
187        // install kernels. It can be assumed they don't have a running kernel if we have no
188        // specifications.
189        if kernel_specifications.is_empty() {
190            return v_flex()
191                .p_4()
192                .size_full()
193                .gap_2()
194                .child(Label::new("No Jupyter Kernels Available").size(LabelSize::Large))
195                .child(
196                    Label::new("To start interactively running code in your editor, you need to install and configure Jupyter kernels.")
197                        .size(LabelSize::Default),
198                )
199                .child(
200                    h_flex().w_full().p_4().justify_center().gap_2().child(
201                        ButtonLike::new("install-kernels")
202                            .style(ButtonStyle::Filled)
203                            .size(ButtonSize::Large)
204                            .layer(ElevationIndex::ModalSurface)
205                            .child(Label::new("Install Kernels"))
206                            .on_click(move |_, cx| {
207                                cx.open_url(
208                                    "https://docs.jupyter.org/en/latest/install/kernels.html",
209                                )
210                            }),
211                    ),
212                )
213                .into_any_element();
214        }
215
216        // When there are no sessions, show the command to run code in an editor
217        if sessions.is_empty() {
218            return v_flex()
219                .p_4()
220                .size_full()
221                .gap_2()
222                .child(Label::new("No Jupyter Kernel Sessions").size(LabelSize::Large))
223                .child(
224                    v_flex().child(
225                        Label::new("To run code in a Jupyter kernel, select some code and use the 'repl::Run' command.")
226                            .size(LabelSize::Default)
227                    )
228                    .children(
229                            KeyBinding::for_action(&Run, cx)
230                            .map(|binding|
231                                binding.into_any_element()
232                            )
233                    )
234                )
235                .child(Label::new("Kernels available").size(LabelSize::Large))
236                .children(
237                    kernel_specifications.into_iter().map(|spec| {
238                        h_flex().gap_2().child(Label::new(spec.name.clone()))
239                            .child(Label::new(spec.kernelspec.language.clone()).color(Color::Muted))
240                    })
241                )
242
243                .into_any_element();
244        }
245
246        v_flex()
247            .p_4()
248            .child(Label::new("Jupyter Kernel Sessions").size(LabelSize::Large))
249            .children(
250                sessions
251                    .into_iter()
252                    .map(|session| session.clone().into_any_element()),
253            )
254            .into_any_element()
255    }
256}