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