1use crate::components::KernelListItem;
2use crate::kernels::RemoteRunningKernel;
3use crate::setup_editor_session_actions;
4use crate::{
5 KernelStatus,
6 kernels::{Kernel, KernelSession, KernelSpecification, NativeRunningKernel},
7 outputs::{
8 ExecutionStatus, ExecutionView, ExecutionViewFinishedEmpty, ExecutionViewFinishedSmall,
9 },
10 repl_settings::ReplSettings,
11};
12use anyhow::Context as _;
13use collections::{HashMap, HashSet};
14use editor::SelectionEffects;
15use editor::{
16 Anchor, AnchorRangeExt as _, Editor, Inlay, MultiBuffer, ToOffset, ToPoint,
17 display_map::{
18 BlockContext, BlockId, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId,
19 RenderBlock,
20 },
21 scroll::Autoscroll,
22};
23use project::InlayId;
24
25/// Marker types
26enum ReplExecutedRange {}
27
28use futures::FutureExt as _;
29use gpui::{
30 Context, Entity, EventEmitter, Render, Subscription, Task, WeakEntity, Window, div, prelude::*,
31};
32use language::Point;
33use project::Fs;
34use runtimelib::{
35 ExecuteRequest, ExecutionState, InterruptRequest, JupyterMessage, JupyterMessageContent,
36 ShutdownRequest,
37};
38use settings::Settings as _;
39use std::{env::temp_dir, ops::Range, sync::Arc, time::Duration};
40use theme::ActiveTheme;
41use ui::{IconButtonShape, Tooltip, prelude::*};
42use util::ResultExt as _;
43
44pub struct Session {
45 fs: Arc<dyn Fs>,
46 editor: WeakEntity<Editor>,
47 pub kernel: Kernel,
48 pub kernel_specification: KernelSpecification,
49
50 blocks: HashMap<String, EditorBlock>,
51 result_inlays: HashMap<String, (InlayId, Range<Anchor>, usize)>,
52 next_inlay_id: usize,
53
54 _subscriptions: Vec<Subscription>,
55}
56
57struct EditorBlock {
58 code_range: Range<Anchor>,
59 invalidation_anchor: Anchor,
60 block_id: CustomBlockId,
61 execution_view: Entity<ExecutionView>,
62}
63
64type CloseBlockFn =
65 Arc<dyn for<'a> Fn(CustomBlockId, &'a mut Window, &mut App) + Send + Sync + 'static>;
66
67impl EditorBlock {
68 fn new(
69 editor: WeakEntity<Editor>,
70 code_range: Range<Anchor>,
71 status: ExecutionStatus,
72 on_close: CloseBlockFn,
73 cx: &mut Context<Session>,
74 ) -> anyhow::Result<Self> {
75 let editor = editor.upgrade().context("editor is not open")?;
76 let workspace = editor.read(cx).workspace().context("workspace dropped")?;
77
78 let execution_view = cx.new(|cx| ExecutionView::new(status, workspace.downgrade(), cx));
79
80 let (block_id, invalidation_anchor) = editor.update(cx, |editor, cx| {
81 let buffer = editor.buffer().clone();
82 let buffer_snapshot = buffer.read(cx).snapshot(cx);
83 let end_point = code_range.end.to_point(&buffer_snapshot);
84 let next_row_start = end_point + Point::new(1, 0);
85 if next_row_start > buffer_snapshot.max_point() {
86 buffer.update(cx, |buffer, cx| {
87 buffer.edit(
88 [(
89 buffer_snapshot.max_point()..buffer_snapshot.max_point(),
90 "\n",
91 )],
92 None,
93 cx,
94 )
95 });
96 }
97
98 // Re-read snapshot after potential buffer edit and create a fresh anchor for
99 // block placement. Using anchor_before (Bias::Left) ensures the anchor stays
100 // at the end of the code line regardless of whether we inserted a newline.
101 let buffer_snapshot = buffer.read(cx).snapshot(cx);
102 let block_placement_anchor = buffer_snapshot.anchor_before(end_point);
103 let invalidation_anchor = buffer_snapshot.anchor_before(next_row_start);
104 let block = BlockProperties {
105 placement: BlockPlacement::Below(block_placement_anchor),
106 // Take up at least one height for status, allow the editor to determine the real height based on the content from render
107 height: Some(1),
108 style: BlockStyle::Sticky,
109 render: Self::create_output_area_renderer(execution_view.clone(), on_close.clone()),
110 priority: 0,
111 };
112
113 let block_id = editor.insert_blocks([block], None, cx)[0];
114 (block_id, invalidation_anchor)
115 });
116
117 anyhow::Ok(Self {
118 code_range,
119 invalidation_anchor,
120 block_id,
121 execution_view,
122 })
123 }
124
125 fn handle_message(
126 &mut self,
127 message: &JupyterMessage,
128 window: &mut Window,
129 cx: &mut Context<Session>,
130 ) {
131 self.execution_view.update(cx, |execution_view, cx| {
132 execution_view.push_message(&message.content, window, cx);
133 });
134 }
135
136 fn create_output_area_renderer(
137 execution_view: Entity<ExecutionView>,
138 on_close: CloseBlockFn,
139 ) -> RenderBlock {
140 Arc::new(move |cx: &mut BlockContext| {
141 let execution_view = execution_view.clone();
142 let text_style = crate::outputs::plain::text_style(cx.window, cx.app);
143
144 let editor_margins = cx.margins;
145 let gutter = editor_margins.gutter;
146
147 let block_id = cx.block_id;
148 let on_close = on_close.clone();
149
150 let rem_size = cx.window.rem_size();
151
152 let text_line_height = text_style.line_height_in_pixels(rem_size);
153 let output_settings = ReplSettings::get_global(cx.app);
154 let output_max_height = if output_settings.output_max_height_lines > 0 {
155 Some(text_line_height * output_settings.output_max_height_lines as f32)
156 } else {
157 None
158 };
159
160 let close_button = h_flex()
161 .flex_none()
162 .items_center()
163 .justify_center()
164 .absolute()
165 .top(text_line_height / 2.)
166 .right(
167 // 2px is a magic number to nudge the button just a bit closer to
168 // the line number start
169 gutter.full_width() / 2.0 - text_line_height / 2.0 - px(2.),
170 )
171 .w(text_line_height)
172 .h(text_line_height)
173 .child(
174 IconButton::new("close_output_area", IconName::Close)
175 .icon_size(IconSize::Small)
176 .icon_color(Color::Muted)
177 .size(ButtonSize::Compact)
178 .shape(IconButtonShape::Square)
179 .tooltip(Tooltip::text("Close output area"))
180 .on_click(move |_, window, cx| {
181 if let BlockId::Custom(block_id) = block_id {
182 (on_close)(block_id, window, cx)
183 }
184 }),
185 );
186
187 div()
188 .id(cx.block_id)
189 .block_mouse_except_scroll()
190 .flex()
191 .items_start()
192 .min_h(text_line_height)
193 .w_full()
194 .border_y_1()
195 .border_color(cx.theme().colors().border)
196 .bg(cx.theme().colors().background)
197 .child(
198 div()
199 .relative()
200 .w(gutter.full_width())
201 .h(text_line_height * 2)
202 .child(close_button),
203 )
204 .child(
205 div()
206 .id((ElementId::from(cx.block_id), "output-scroll"))
207 .flex_1()
208 .overflow_x_hidden()
209 .py(text_line_height / 2.)
210 .mr(editor_margins.right)
211 .pr_2()
212 .when_some(output_max_height, |div, max_h| {
213 div.max_h(max_h).overflow_y_scroll()
214 })
215 .child(execution_view),
216 )
217 .into_any_element()
218 })
219 }
220}
221
222impl Session {
223 pub fn new(
224 editor: WeakEntity<Editor>,
225 fs: Arc<dyn Fs>,
226 kernel_specification: KernelSpecification,
227 window: &mut Window,
228 cx: &mut Context<Self>,
229 ) -> Self {
230 let subscription = match editor.upgrade() {
231 Some(editor) => {
232 let buffer = editor.read(cx).buffer().clone();
233 cx.subscribe(&buffer, Self::on_buffer_event)
234 }
235 None => Subscription::new(|| {}),
236 };
237
238 let editor_handle = editor.clone();
239
240 editor
241 .update(cx, |editor, _cx| {
242 setup_editor_session_actions(editor, editor_handle);
243 })
244 .ok();
245
246 let mut session = Self {
247 fs,
248 editor,
249 kernel: Kernel::StartingKernel(Task::ready(()).shared()),
250 blocks: HashMap::default(),
251 result_inlays: HashMap::default(),
252 next_inlay_id: 0,
253 kernel_specification,
254 _subscriptions: vec![subscription],
255 };
256
257 session.start_kernel(window, cx);
258 session
259 }
260
261 fn start_kernel(&mut self, window: &mut Window, cx: &mut Context<Self>) {
262 let kernel_language = self.kernel_specification.language();
263 let entity_id = self.editor.entity_id();
264 let working_directory = self
265 .editor
266 .upgrade()
267 .and_then(|editor| editor.read(cx).working_directory(cx))
268 .unwrap_or_else(temp_dir);
269
270 telemetry::event!(
271 "Kernel Status Changed",
272 kernel_language,
273 kernel_status = KernelStatus::Starting.to_string(),
274 repl_session_id = cx.entity_id().to_string(),
275 );
276
277 let session_view = cx.entity();
278
279 let kernel = match self.kernel_specification.clone() {
280 KernelSpecification::Jupyter(kernel_specification) => NativeRunningKernel::new(
281 kernel_specification,
282 entity_id,
283 working_directory,
284 self.fs.clone(),
285 session_view,
286 window,
287 cx,
288 ),
289 KernelSpecification::PythonEnv(env_specification) => NativeRunningKernel::new(
290 env_specification.as_local_spec(),
291 entity_id,
292 working_directory,
293 self.fs.clone(),
294 session_view,
295 window,
296 cx,
297 ),
298 KernelSpecification::Remote(remote_kernel_specification) => RemoteRunningKernel::new(
299 remote_kernel_specification,
300 working_directory,
301 session_view,
302 window,
303 cx,
304 ),
305 };
306
307 let pending_kernel = cx
308 .spawn(async move |this, cx| {
309 let kernel = kernel.await;
310
311 match kernel {
312 Ok(kernel) => {
313 this.update(cx, |session, cx| {
314 session.kernel(Kernel::RunningKernel(kernel), cx);
315 })
316 .ok();
317 }
318 Err(err) => {
319 this.update(cx, |session, cx| {
320 session.kernel_errored(err.to_string(), cx);
321 })
322 .ok();
323 }
324 }
325 })
326 .shared();
327
328 self.kernel(Kernel::StartingKernel(pending_kernel), cx);
329 cx.notify();
330 }
331
332 pub fn kernel_errored(&mut self, error_message: String, cx: &mut Context<Self>) {
333 self.kernel(Kernel::ErroredLaunch(error_message.clone()), cx);
334
335 self.blocks.values().for_each(|block| {
336 block.execution_view.update(cx, |execution_view, cx| {
337 match execution_view.status {
338 ExecutionStatus::Finished => {
339 // Do nothing when the output was good
340 }
341 _ => {
342 // All other cases, set the status to errored
343 execution_view.status =
344 ExecutionStatus::KernelErrored(error_message.clone())
345 }
346 }
347 cx.notify();
348 });
349 });
350 }
351
352 fn on_buffer_event(
353 &mut self,
354 buffer: Entity<MultiBuffer>,
355 event: &multi_buffer::Event,
356 cx: &mut Context<Self>,
357 ) {
358 if let multi_buffer::Event::Edited { .. } = event {
359 let snapshot = buffer.read(cx).snapshot(cx);
360
361 let mut blocks_to_remove: HashSet<CustomBlockId> = HashSet::default();
362 let mut gutter_ranges_to_remove: Vec<Range<Anchor>> = Vec::new();
363 let mut keys_to_remove: Vec<String> = Vec::new();
364
365 self.blocks.retain(|id, block| {
366 if block.invalidation_anchor.is_valid(&snapshot) {
367 true
368 } else {
369 blocks_to_remove.insert(block.block_id);
370 gutter_ranges_to_remove.push(block.code_range.clone());
371 keys_to_remove.push(id.clone());
372 false
373 }
374 });
375
376 let mut inlays_to_remove: Vec<InlayId> = Vec::new();
377
378 self.result_inlays
379 .retain(|id, (inlay_id, code_range, original_len)| {
380 let start_offset = code_range.start.to_offset(&snapshot);
381 let end_offset = code_range.end.to_offset(&snapshot);
382 let current_len = end_offset.saturating_sub(start_offset);
383
384 if current_len != *original_len {
385 inlays_to_remove.push(*inlay_id);
386 gutter_ranges_to_remove.push(code_range.clone());
387 keys_to_remove.push(id.clone());
388 false
389 } else {
390 true
391 }
392 });
393
394 if !blocks_to_remove.is_empty()
395 || !inlays_to_remove.is_empty()
396 || !gutter_ranges_to_remove.is_empty()
397 {
398 self.editor
399 .update(cx, |editor, cx| {
400 if !blocks_to_remove.is_empty() {
401 editor.remove_blocks(blocks_to_remove, None, cx);
402 }
403 if !inlays_to_remove.is_empty() {
404 editor.splice_inlays(&inlays_to_remove, vec![], cx);
405 }
406 if !gutter_ranges_to_remove.is_empty() {
407 editor.remove_gutter_highlights::<ReplExecutedRange>(
408 gutter_ranges_to_remove,
409 cx,
410 );
411 }
412 })
413 .ok();
414 cx.notify();
415 }
416 }
417 }
418
419 fn send(&mut self, message: JupyterMessage, _cx: &mut Context<Self>) -> anyhow::Result<()> {
420 if let Kernel::RunningKernel(kernel) = &mut self.kernel {
421 kernel.request_tx().try_send(message).ok();
422 }
423
424 anyhow::Ok(())
425 }
426
427 fn replace_block_with_inlay(&mut self, message_id: &str, text: &str, cx: &mut Context<Self>) {
428 let Some(block) = self.blocks.remove(message_id) else {
429 return;
430 };
431
432 let Some(editor) = self.editor.upgrade() else {
433 return;
434 };
435
436 let code_range = block.code_range.clone();
437
438 editor.update(cx, |editor, cx| {
439 let mut block_ids = HashSet::default();
440 block_ids.insert(block.block_id);
441 editor.remove_blocks(block_ids, None, cx);
442
443 let buffer = editor.buffer().read(cx).snapshot(cx);
444 let start_offset = code_range.start.to_offset(&buffer);
445 let end_offset = code_range.end.to_offset(&buffer);
446 let original_len = end_offset.saturating_sub(start_offset);
447
448 let end_point = code_range.end.to_point(&buffer);
449 let inlay_position = buffer.anchor_after(end_point);
450
451 let inlay_id = self.next_inlay_id;
452 self.next_inlay_id += 1;
453
454 let inlay = Inlay::repl_result(inlay_id, inlay_position, format!(" {}", text));
455
456 editor.splice_inlays(&[], vec![inlay], cx);
457 self.result_inlays.insert(
458 message_id.to_string(),
459 (
460 InlayId::ReplResult(inlay_id),
461 code_range.clone(),
462 original_len,
463 ),
464 );
465
466 editor.insert_gutter_highlight::<ReplExecutedRange>(
467 code_range,
468 |cx| cx.theme().status().success,
469 cx,
470 );
471 });
472
473 cx.notify();
474 }
475
476 pub fn clear_outputs(&mut self, cx: &mut Context<Self>) {
477 let blocks_to_remove: HashSet<CustomBlockId> =
478 self.blocks.values().map(|block| block.block_id).collect();
479
480 let inlays_to_remove: Vec<InlayId> =
481 self.result_inlays.values().map(|(id, _, _)| *id).collect();
482
483 self.editor
484 .update(cx, |editor, cx| {
485 editor.remove_blocks(blocks_to_remove, None, cx);
486 editor.splice_inlays(&inlays_to_remove, vec![], cx);
487 editor.clear_gutter_highlights::<ReplExecutedRange>(cx);
488 })
489 .ok();
490
491 self.blocks.clear();
492 self.result_inlays.clear();
493 }
494
495 pub fn execute(
496 &mut self,
497 code: String,
498 anchor_range: Range<Anchor>,
499 next_cell: Option<Anchor>,
500 move_down: bool,
501 window: &mut Window,
502 cx: &mut Context<Self>,
503 ) {
504 let Some(editor) = self.editor.upgrade() else {
505 return;
506 };
507
508 if code.is_empty() {
509 return;
510 }
511
512 let execute_request = ExecuteRequest {
513 code,
514 ..ExecuteRequest::default()
515 };
516
517 let message: JupyterMessage = execute_request.into();
518
519 let mut blocks_to_remove: HashSet<CustomBlockId> = HashSet::default();
520 let mut inlays_to_remove: Vec<InlayId> = Vec::new();
521 let mut gutter_ranges_to_remove: Vec<Range<Anchor>> = Vec::new();
522
523 let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
524
525 self.blocks.retain(|_key, block| {
526 if anchor_range.overlaps(&block.code_range, &buffer) {
527 blocks_to_remove.insert(block.block_id);
528 false
529 } else {
530 true
531 }
532 });
533
534 self.result_inlays
535 .retain(|_key, (inlay_id, inlay_range, _)| {
536 if anchor_range.overlaps(inlay_range, &buffer) {
537 inlays_to_remove.push(*inlay_id);
538 gutter_ranges_to_remove.push(inlay_range.clone());
539 false
540 } else {
541 true
542 }
543 });
544
545 self.editor
546 .update(cx, |editor, cx| {
547 editor.remove_blocks(blocks_to_remove, None, cx);
548 if !inlays_to_remove.is_empty() {
549 editor.splice_inlays(&inlays_to_remove, vec![], cx);
550 }
551 if !gutter_ranges_to_remove.is_empty() {
552 editor
553 .remove_gutter_highlights::<ReplExecutedRange>(gutter_ranges_to_remove, cx);
554 }
555 })
556 .ok();
557
558 let status = match &self.kernel {
559 Kernel::Restarting => ExecutionStatus::Restarting,
560 Kernel::RunningKernel(_) => ExecutionStatus::Queued,
561 Kernel::StartingKernel(_) => ExecutionStatus::ConnectingToKernel,
562 Kernel::ErroredLaunch(error) => ExecutionStatus::KernelErrored(error.clone()),
563 Kernel::ShuttingDown => ExecutionStatus::ShuttingDown,
564 Kernel::Shutdown => ExecutionStatus::Shutdown,
565 };
566
567 let parent_message_id = message.header.msg_id.clone();
568 let session_view = cx.entity().downgrade();
569 let weak_editor = self.editor.clone();
570 let code_range_for_close = anchor_range.clone();
571
572 let on_close: CloseBlockFn = Arc::new(
573 move |block_id: CustomBlockId, _: &mut Window, cx: &mut App| {
574 if let Some(session) = session_view.upgrade() {
575 session.update(cx, |session, cx| {
576 session.blocks.remove(&parent_message_id);
577 cx.notify();
578 });
579 }
580
581 if let Some(editor) = weak_editor.upgrade() {
582 editor.update(cx, |editor, cx| {
583 let mut block_ids = HashSet::default();
584 block_ids.insert(block_id);
585 editor.remove_blocks(block_ids, None, cx);
586 editor.remove_gutter_highlights::<ReplExecutedRange>(
587 vec![code_range_for_close.clone()],
588 cx,
589 );
590 });
591 }
592 },
593 );
594
595 let Ok(editor_block) = EditorBlock::new(
596 self.editor.clone(),
597 anchor_range.clone(),
598 status,
599 on_close,
600 cx,
601 ) else {
602 return;
603 };
604
605 self.editor
606 .update(cx, |editor, cx| {
607 editor.insert_gutter_highlight::<ReplExecutedRange>(
608 anchor_range.clone(),
609 |cx| cx.theme().status().success,
610 cx,
611 );
612 })
613 .ok();
614
615 let new_cursor_pos = if let Some(next_cursor) = next_cell {
616 next_cursor
617 } else {
618 editor_block.invalidation_anchor
619 };
620
621 let msg_id = message.header.msg_id.clone();
622 let subscription = cx.subscribe(
623 &editor_block.execution_view,
624 move |session, _execution_view, _event: &ExecutionViewFinishedEmpty, cx| {
625 session.replace_block_with_inlay(&msg_id, "✓", cx);
626 },
627 );
628 self._subscriptions.push(subscription);
629
630 let msg_id = message.header.msg_id.clone();
631 let subscription = cx.subscribe(
632 &editor_block.execution_view,
633 move |session, _execution_view, event: &ExecutionViewFinishedSmall, cx| {
634 session.replace_block_with_inlay(&msg_id, &event.0, cx);
635 },
636 );
637 self._subscriptions.push(subscription);
638
639 self.blocks
640 .insert(message.header.msg_id.clone(), editor_block);
641
642 match &self.kernel {
643 Kernel::RunningKernel(_) => {
644 self.send(message, cx).ok();
645 }
646 Kernel::StartingKernel(task) => {
647 // Queue up the execution as a task to run after the kernel starts
648 let task = task.clone();
649
650 cx.spawn(async move |this, cx| {
651 task.await;
652 this.update(cx, |session, cx| {
653 session.send(message, cx).ok();
654 })
655 .ok();
656 })
657 .detach();
658 }
659 _ => {}
660 }
661
662 if move_down {
663 editor.update(cx, move |editor, cx| {
664 editor.change_selections(
665 SelectionEffects::scroll(Autoscroll::top_relative(8)),
666 window,
667 cx,
668 |selections| {
669 selections.select_ranges([new_cursor_pos..new_cursor_pos]);
670 },
671 );
672 });
673 }
674 }
675
676 pub fn interrupt(&mut self, cx: &mut Context<Self>) {
677 match &mut self.kernel {
678 Kernel::RunningKernel(_kernel) => {
679 self.send(InterruptRequest {}.into(), cx).ok();
680 }
681 Kernel::StartingKernel(_task) => {
682 // NOTE: If we switch to a literal queue instead of chaining on to the task, clear all queued executions
683 }
684 _ => {}
685 }
686 }
687
688 pub fn kernel(&mut self, kernel: Kernel, cx: &mut Context<Self>) {
689 if let Kernel::Shutdown = kernel {
690 cx.emit(SessionEvent::Shutdown(self.editor.clone()));
691 }
692
693 let kernel_status = KernelStatus::from(&kernel).to_string();
694 let kernel_language = self.kernel_specification.language();
695
696 telemetry::event!(
697 "Kernel Status Changed",
698 kernel_language,
699 kernel_status,
700 repl_session_id = cx.entity_id().to_string(),
701 );
702
703 self.kernel = kernel;
704 }
705
706 pub fn shutdown(&mut self, window: &mut Window, cx: &mut Context<Self>) {
707 let kernel = std::mem::replace(&mut self.kernel, Kernel::ShuttingDown);
708
709 match kernel {
710 Kernel::RunningKernel(mut kernel) => {
711 let mut request_tx = kernel.request_tx();
712
713 let forced = kernel.force_shutdown(window, cx);
714
715 cx.spawn(async move |this, cx| {
716 let message: JupyterMessage = ShutdownRequest { restart: false }.into();
717 request_tx.try_send(message).ok();
718
719 forced.await.log_err();
720
721 // Give the kernel a bit of time to clean up
722 cx.background_executor().timer(Duration::from_secs(3)).await;
723
724 this.update(cx, |session, cx| {
725 session.clear_outputs(cx);
726 session.kernel(Kernel::Shutdown, cx);
727 cx.notify();
728 })
729 .ok();
730 })
731 .detach();
732 }
733 _ => {
734 self.kernel(Kernel::Shutdown, cx);
735 }
736 }
737 cx.notify();
738 }
739
740 pub fn restart(&mut self, window: &mut Window, cx: &mut Context<Self>) {
741 let kernel = std::mem::replace(&mut self.kernel, Kernel::Restarting);
742
743 match kernel {
744 Kernel::Restarting => {
745 // Do nothing if already restarting
746 }
747 Kernel::RunningKernel(mut kernel) => {
748 let mut request_tx = kernel.request_tx();
749
750 let forced = kernel.force_shutdown(window, cx);
751
752 cx.spawn_in(window, async move |this, cx| {
753 // Send shutdown request with restart flag
754 log::debug!("restarting kernel");
755 let message: JupyterMessage = ShutdownRequest { restart: true }.into();
756 request_tx.try_send(message).ok();
757
758 // Wait for kernel to shutdown
759 cx.background_executor().timer(Duration::from_secs(1)).await;
760
761 // Force kill the kernel if it hasn't shut down
762 forced.await.log_err();
763
764 // Start a new kernel
765 this.update_in(cx, |session, window, cx| {
766 // TODO: Differentiate between restart and restart+clear-outputs
767 session.clear_outputs(cx);
768 session.start_kernel(window, cx);
769 })
770 .ok();
771 })
772 .detach();
773 }
774 _ => {
775 self.clear_outputs(cx);
776 self.start_kernel(window, cx);
777 }
778 }
779 cx.notify();
780 }
781}
782
783pub enum SessionEvent {
784 Shutdown(WeakEntity<Editor>),
785}
786
787impl EventEmitter<SessionEvent> for Session {}
788
789impl Render for Session {
790 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
791 let (status_text, interrupt_button) = match &self.kernel {
792 Kernel::RunningKernel(kernel) => (
793 kernel
794 .kernel_info()
795 .as_ref()
796 .map(|info| info.language_info.name.clone()),
797 Some(
798 Button::new("interrupt", "Interrupt")
799 .style(ButtonStyle::Subtle)
800 .on_click(cx.listener(move |session, _, _, cx| {
801 session.interrupt(cx);
802 })),
803 ),
804 ),
805 Kernel::StartingKernel(_) => (Some("Starting".into()), None),
806 Kernel::ErroredLaunch(err) => (Some(format!("Error: {err}")), None),
807 Kernel::ShuttingDown => (Some("Shutting Down".into()), None),
808 Kernel::Shutdown => (Some("Shutdown".into()), None),
809 Kernel::Restarting => (Some("Restarting".into()), None),
810 };
811
812 KernelListItem::new(self.kernel_specification.clone())
813 .status_color(match &self.kernel {
814 Kernel::RunningKernel(kernel) => match kernel.execution_state() {
815 ExecutionState::Idle => Color::Success,
816 ExecutionState::Busy => Color::Modified,
817 ExecutionState::Unknown => Color::Modified,
818 ExecutionState::Starting => Color::Modified,
819 ExecutionState::Restarting => Color::Modified,
820 ExecutionState::Terminating => Color::Disabled,
821 ExecutionState::AutoRestarting => Color::Modified,
822 ExecutionState::Dead => Color::Disabled,
823 ExecutionState::Other(_) => Color::Modified,
824 },
825 Kernel::StartingKernel(_) => Color::Modified,
826 Kernel::ErroredLaunch(_) => Color::Error,
827 Kernel::ShuttingDown => Color::Modified,
828 Kernel::Shutdown => Color::Disabled,
829 Kernel::Restarting => Color::Modified,
830 })
831 .child(Label::new(self.kernel_specification.name()))
832 .children(status_text.map(|status_text| Label::new(format!("({status_text})"))))
833 .button(
834 Button::new("shutdown", "Shutdown")
835 .style(ButtonStyle::Subtle)
836 .disabled(self.kernel.is_shutting_down())
837 .on_click(cx.listener(move |session, _, window, cx| {
838 session.shutdown(window, cx);
839 })),
840 )
841 .buttons(interrupt_button)
842 }
843}
844
845impl KernelSession for Session {
846 fn route(&mut self, message: &JupyterMessage, window: &mut Window, cx: &mut Context<Self>) {
847 let parent_message_id = match message.parent_header.as_ref() {
848 Some(header) => &header.msg_id,
849 None => return,
850 };
851
852 match &message.content {
853 JupyterMessageContent::Status(status) => {
854 self.kernel.set_execution_state(&status.execution_state);
855
856 telemetry::event!(
857 "Kernel Status Changed",
858 kernel_language = self.kernel_specification.language(),
859 kernel_status = KernelStatus::from(&self.kernel).to_string(),
860 repl_session_id = cx.entity_id().to_string(),
861 );
862
863 cx.notify();
864 }
865 JupyterMessageContent::KernelInfoReply(reply) => {
866 self.kernel.set_kernel_info(reply);
867 cx.notify();
868 }
869 JupyterMessageContent::UpdateDisplayData(update) => {
870 let display_id = if let Some(display_id) = update.transient.display_id.clone() {
871 display_id
872 } else {
873 return;
874 };
875
876 self.blocks.iter_mut().for_each(|(_, block)| {
877 block.execution_view.update(cx, |execution_view, cx| {
878 execution_view.update_display_data(&update.data, &display_id, window, cx);
879 });
880 });
881 return;
882 }
883 _ => {}
884 }
885
886 if let Some(block) = self.blocks.get_mut(parent_message_id) {
887 block.handle_message(message, window, cx);
888 }
889 }
890
891 fn kernel_errored(&mut self, error_message: String, cx: &mut Context<Self>) {
892 self.kernel_errored(error_message, cx);
893 }
894}