1//! REPL operations on an [`Editor`].
2
3use std::ops::Range;
4use std::sync::Arc;
5
6use anyhow::{Context, Result};
7use editor::{Anchor, Editor, RangeToAnchorExt};
8use gpui::{prelude::*, AppContext, View, WeakView, WindowContext};
9use language::{Language, Point};
10use multi_buffer::MultiBufferRow;
11
12use crate::repl_store::ReplStore;
13use crate::session::SessionEvent;
14use crate::{KernelSpecification, Session};
15
16pub fn run(editor: WeakView<Editor>, cx: &mut WindowContext) -> Result<()> {
17 let store = ReplStore::global(cx);
18 if !store.read(cx).is_enabled() {
19 return Ok(());
20 }
21
22 let (selected_text, language, anchor_range) = match snippet(editor.clone(), cx) {
23 Some(snippet) => snippet,
24 None => return Ok(()),
25 };
26
27 let entity_id = editor.entity_id();
28
29 let kernel_specification = store.update(cx, |store, cx| {
30 store
31 .kernelspec(&language, cx)
32 .with_context(|| format!("No kernel found for language: {}", language.name()))
33 })?;
34
35 let fs = store.read(cx).fs().clone();
36 let session = if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
37 session
38 } else {
39 let session = cx.new_view(|cx| Session::new(editor.clone(), fs, kernel_specification, cx));
40
41 editor.update(cx, |_editor, cx| {
42 cx.notify();
43
44 cx.subscribe(&session, {
45 let store = store.clone();
46 move |_this, _session, event, cx| match event {
47 SessionEvent::Shutdown(shutdown_event) => {
48 store.update(cx, |store, _cx| {
49 store.remove_session(shutdown_event.entity_id());
50 });
51 }
52 }
53 })
54 .detach();
55 })?;
56
57 store.update(cx, |store, _cx| {
58 store.insert_session(entity_id, session.clone());
59 });
60
61 session
62 };
63
64 session.update(cx, |session, cx| {
65 session.execute(&selected_text, anchor_range, cx);
66 });
67
68 anyhow::Ok(())
69}
70
71pub enum SessionSupport {
72 ActiveSession(View<Session>),
73 Inactive(Box<KernelSpecification>),
74 RequiresSetup(Arc<str>),
75 Unsupported,
76}
77
78pub fn session(editor: WeakView<Editor>, cx: &mut AppContext) -> SessionSupport {
79 let store = ReplStore::global(cx);
80 let entity_id = editor.entity_id();
81
82 if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
83 return SessionSupport::ActiveSession(session);
84 };
85
86 let language = get_language(editor, cx);
87 let language = match language {
88 Some(language) => language,
89 None => return SessionSupport::Unsupported,
90 };
91 let kernelspec = store.update(cx, |store, cx| store.kernelspec(&language, cx));
92
93 match kernelspec {
94 Some(kernelspec) => SessionSupport::Inactive(Box::new(kernelspec)),
95 None => match language.name().as_ref() {
96 "TypeScript" | "Python" => SessionSupport::RequiresSetup(language.name()),
97 _ => SessionSupport::Unsupported,
98 },
99 }
100}
101
102pub fn clear_outputs(editor: WeakView<Editor>, cx: &mut WindowContext) {
103 let store = ReplStore::global(cx);
104 let entity_id = editor.entity_id();
105 if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
106 session.update(cx, |session, cx| {
107 session.clear_outputs(cx);
108 cx.notify();
109 });
110 }
111}
112
113pub fn interrupt(editor: WeakView<Editor>, cx: &mut WindowContext) {
114 let store = ReplStore::global(cx);
115 let entity_id = editor.entity_id();
116 if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
117 session.update(cx, |session, cx| {
118 session.interrupt(cx);
119 cx.notify();
120 });
121 }
122}
123
124pub fn shutdown(editor: WeakView<Editor>, cx: &mut WindowContext) {
125 let store = ReplStore::global(cx);
126 let entity_id = editor.entity_id();
127 if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
128 session.update(cx, |session, cx| {
129 session.shutdown(cx);
130 cx.notify();
131 });
132 }
133}
134
135fn snippet(
136 editor: WeakView<Editor>,
137 cx: &mut WindowContext,
138) -> Option<(String, Arc<Language>, Range<Anchor>)> {
139 let editor = editor.upgrade()?;
140 let editor = editor.read(cx);
141
142 let buffer = editor.buffer().read(cx).snapshot(cx);
143
144 let selection = editor.selections.newest::<usize>(cx);
145 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
146
147 let range = if selection.is_empty() {
148 let cursor = selection.head();
149
150 let cursor_row = multi_buffer_snapshot.offset_to_point(cursor).row;
151 let start_offset = multi_buffer_snapshot.point_to_offset(Point::new(cursor_row, 0));
152
153 let end_point = Point::new(
154 cursor_row,
155 multi_buffer_snapshot.line_len(MultiBufferRow(cursor_row)),
156 );
157 let end_offset = start_offset.saturating_add(end_point.column as usize);
158
159 // Create a range from the start to the end of the line
160 start_offset..end_offset
161 } else {
162 selection.range()
163 };
164
165 let anchor_range = range.to_anchors(&multi_buffer_snapshot);
166
167 let selected_text = buffer
168 .text_for_range(anchor_range.clone())
169 .collect::<String>();
170
171 let start_language = buffer.language_at(anchor_range.start)?;
172 let end_language = buffer.language_at(anchor_range.end)?;
173 if start_language != end_language {
174 return None;
175 }
176
177 Some((selected_text, start_language.clone(), anchor_range))
178}
179
180fn get_language(editor: WeakView<Editor>, cx: &mut AppContext) -> Option<Arc<Language>> {
181 let editor = editor.upgrade()?;
182 let selection = editor.read(cx).selections.newest::<usize>(cx);
183 let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
184 buffer.language_at(selection.head()).cloned()
185}