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}