repl: Replace REPL panel with sessions view (#14981)
This PR removes the REPL panel and replaces it with a new sessions view that gets displayed in its own pane. The sessions view can be opened with the `repl: sessions` command (we can adjust the name, as needed). There was a rather in-depth refactoring needed to extricate the various REPL functionality on the editor from the `RuntimePanel`. <img width="1136" alt="Screenshot 2024-07-22 at 4 12 12 PM" src="https://github.com/user-attachments/assets/ac0da351-778e-4200-b08c-39f9e77d78bf"> <img width="1136" alt="Screenshot 2024-07-22 at 4 12 17 PM" src="https://github.com/user-attachments/assets/6ca53476-6ac4-4f8b-afc8-f7863f7065c7"> Release Notes: - N/A
This commit is contained in:
parent
8f20ea1093
commit
d8a42bbf63
9 changed files with 474 additions and 637 deletions
185
crates/repl/src/repl_editor.rs
Normal file
185
crates/repl/src/repl_editor.rs
Normal file
|
@ -0,0 +1,185 @@
|
|||
//! REPL operations on an [`Editor`].
|
||||
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use editor::{Anchor, Editor, RangeToAnchorExt};
|
||||
use gpui::{prelude::*, AppContext, View, WeakView, WindowContext};
|
||||
use language::{Language, Point};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
|
||||
use crate::repl_store::ReplStore;
|
||||
use crate::session::SessionEvent;
|
||||
use crate::{KernelSpecification, Session};
|
||||
|
||||
pub fn run(editor: WeakView<Editor>, cx: &mut WindowContext) -> Result<()> {
|
||||
let store = ReplStore::global(cx);
|
||||
if !store.read(cx).is_enabled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (selected_text, language, anchor_range) = match snippet(editor.clone(), cx) {
|
||||
Some(snippet) => snippet,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let entity_id = editor.entity_id();
|
||||
|
||||
let kernel_specification = store.update(cx, |store, cx| {
|
||||
store
|
||||
.kernelspec(&language, cx)
|
||||
.with_context(|| format!("No kernel found for language: {}", language.name()))
|
||||
})?;
|
||||
|
||||
let fs = store.read(cx).fs().clone();
|
||||
let session = if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||
session
|
||||
} else {
|
||||
let session = cx.new_view(|cx| Session::new(editor.clone(), fs, kernel_specification, cx));
|
||||
|
||||
editor.update(cx, |_editor, cx| {
|
||||
cx.notify();
|
||||
|
||||
cx.subscribe(&session, {
|
||||
let store = store.clone();
|
||||
move |_this, _session, event, cx| match event {
|
||||
SessionEvent::Shutdown(shutdown_event) => {
|
||||
store.update(cx, |store, _cx| {
|
||||
store.remove_session(shutdown_event.entity_id());
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})?;
|
||||
|
||||
store.update(cx, |store, _cx| {
|
||||
store.insert_session(entity_id, session.clone());
|
||||
});
|
||||
|
||||
session
|
||||
};
|
||||
|
||||
session.update(cx, |session, cx| {
|
||||
session.execute(&selected_text, anchor_range, cx);
|
||||
});
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
pub enum SessionSupport {
|
||||
ActiveSession(View<Session>),
|
||||
Inactive(Box<KernelSpecification>),
|
||||
RequiresSetup(Arc<str>),
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
pub fn session(editor: WeakView<Editor>, cx: &mut AppContext) -> SessionSupport {
|
||||
let store = ReplStore::global(cx);
|
||||
let entity_id = editor.entity_id();
|
||||
|
||||
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||
return SessionSupport::ActiveSession(session);
|
||||
};
|
||||
|
||||
let language = get_language(editor, cx);
|
||||
let language = match language {
|
||||
Some(language) => language,
|
||||
None => return SessionSupport::Unsupported,
|
||||
};
|
||||
let kernelspec = store.update(cx, |store, cx| store.kernelspec(&language, cx));
|
||||
|
||||
match kernelspec {
|
||||
Some(kernelspec) => SessionSupport::Inactive(Box::new(kernelspec)),
|
||||
None => match language.name().as_ref() {
|
||||
"TypeScript" | "Python" => SessionSupport::RequiresSetup(language.name()),
|
||||
_ => SessionSupport::Unsupported,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_outputs(editor: WeakView<Editor>, cx: &mut WindowContext) {
|
||||
let store = ReplStore::global(cx);
|
||||
let entity_id = editor.entity_id();
|
||||
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||
session.update(cx, |session, cx| {
|
||||
session.clear_outputs(cx);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interrupt(editor: WeakView<Editor>, cx: &mut WindowContext) {
|
||||
let store = ReplStore::global(cx);
|
||||
let entity_id = editor.entity_id();
|
||||
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||
session.update(cx, |session, cx| {
|
||||
session.interrupt(cx);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown(editor: WeakView<Editor>, cx: &mut WindowContext) {
|
||||
let store = ReplStore::global(cx);
|
||||
let entity_id = editor.entity_id();
|
||||
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||
session.update(cx, |session, cx| {
|
||||
session.shutdown(cx);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn snippet(
|
||||
editor: WeakView<Editor>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<(String, Arc<Language>, Range<Anchor>)> {
|
||||
let editor = editor.upgrade()?;
|
||||
let editor = editor.read(cx);
|
||||
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
let selection = editor.selections.newest::<usize>(cx);
|
||||
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
let range = if selection.is_empty() {
|
||||
let cursor = selection.head();
|
||||
|
||||
let cursor_row = multi_buffer_snapshot.offset_to_point(cursor).row;
|
||||
let start_offset = multi_buffer_snapshot.point_to_offset(Point::new(cursor_row, 0));
|
||||
|
||||
let end_point = Point::new(
|
||||
cursor_row,
|
||||
multi_buffer_snapshot.line_len(MultiBufferRow(cursor_row)),
|
||||
);
|
||||
let end_offset = start_offset.saturating_add(end_point.column as usize);
|
||||
|
||||
// Create a range from the start to the end of the line
|
||||
start_offset..end_offset
|
||||
} else {
|
||||
selection.range()
|
||||
};
|
||||
|
||||
let anchor_range = range.to_anchors(&multi_buffer_snapshot);
|
||||
|
||||
let selected_text = buffer
|
||||
.text_for_range(anchor_range.clone())
|
||||
.collect::<String>();
|
||||
|
||||
let start_language = buffer.language_at(anchor_range.start)?;
|
||||
let end_language = buffer.language_at(anchor_range.end)?;
|
||||
if start_language != end_language {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((selected_text, start_language.clone(), anchor_range))
|
||||
}
|
||||
|
||||
fn get_language(editor: WeakView<Editor>, cx: &mut AppContext) -> Option<Arc<Language>> {
|
||||
let editor = editor.upgrade()?;
|
||||
let selection = editor.read(cx).selections.newest::<usize>(cx);
|
||||
let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
buffer.language_at(selection.head()).cloned()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue