repl_editor.rs

  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}