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}