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