Add kernel detection for language support of runnable markdown cells (#29664)
Closes #27757 Release Notes: - List of runnable markdown cells is now based on detected jupyter kernels instead of hardcoded to Python and TypeScript
This commit is contained in:
parent
dce22a965e
commit
66667d1eef
2 changed files with 82 additions and 18 deletions
|
@ -97,7 +97,7 @@ pub fn run(
|
|||
};
|
||||
|
||||
let (runnable_ranges, next_cell_point) =
|
||||
runnable_ranges(&buffer.read(cx).snapshot(), selected_range);
|
||||
runnable_ranges(&buffer.read(cx).snapshot(), selected_range, cx);
|
||||
|
||||
for runnable_range in runnable_ranges {
|
||||
let Some(language) = multibuffer.read(cx).language_at(runnable_range.start, cx) else {
|
||||
|
@ -215,7 +215,8 @@ pub fn session(editor: WeakEntity<Editor>, cx: &mut App) -> SessionSupport {
|
|||
match kernelspec {
|
||||
Some(kernelspec) => SessionSupport::Inactive(kernelspec),
|
||||
None => {
|
||||
if language_supported(&language.clone()) {
|
||||
// For language_supported, need to check available kernels for language
|
||||
if language_supported(&language.clone(), cx) {
|
||||
SessionSupport::RequiresSetup(language.name())
|
||||
} else {
|
||||
SessionSupport::Unsupported
|
||||
|
@ -414,10 +415,11 @@ fn jupytext_cells(
|
|||
fn runnable_ranges(
|
||||
buffer: &BufferSnapshot,
|
||||
range: Range<Point>,
|
||||
cx: &mut App,
|
||||
) -> (Vec<Range<Point>>, Option<Point>) {
|
||||
if let Some(language) = buffer.language() {
|
||||
if language.name() == "Markdown".into() {
|
||||
return (markdown_code_blocks(buffer, range.clone()), None);
|
||||
return (markdown_code_blocks(buffer, range.clone(), cx), None);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -442,21 +444,30 @@ fn runnable_ranges(
|
|||
|
||||
// We allow markdown code blocks to end in a trailing newline in order to render the output
|
||||
// below the final code fence. This is different than our behavior for selections and Jupytext cells.
|
||||
fn markdown_code_blocks(buffer: &BufferSnapshot, range: Range<Point>) -> Vec<Range<Point>> {
|
||||
fn markdown_code_blocks(
|
||||
buffer: &BufferSnapshot,
|
||||
range: Range<Point>,
|
||||
cx: &mut App,
|
||||
) -> Vec<Range<Point>> {
|
||||
buffer
|
||||
.injections_intersecting_range(range)
|
||||
.filter(|(_, language)| language_supported(language))
|
||||
.filter(|(_, language)| language_supported(language, cx))
|
||||
.map(|(content_range, _)| {
|
||||
buffer.offset_to_point(content_range.start)..buffer.offset_to_point(content_range.end)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn language_supported(language: &Arc<Language>) -> bool {
|
||||
match language.name().as_ref() {
|
||||
"TypeScript" | "Python" => true,
|
||||
_ => false,
|
||||
}
|
||||
fn language_supported(language: &Arc<Language>, cx: &mut App) -> bool {
|
||||
let store = ReplStore::global(cx);
|
||||
let store_read = store.read(cx);
|
||||
|
||||
// Since we're just checking for general language support, we only need to look at
|
||||
// the pure Jupyter kernels - these are all the globally available ones
|
||||
store_read.pure_jupyter_kernel_specifications().any(|spec| {
|
||||
// Convert to lowercase for case-insensitive comparison since kernels might report "python" while our language is "Python"
|
||||
spec.language().as_ref().to_lowercase() == language.name().as_ref().to_lowercase()
|
||||
})
|
||||
}
|
||||
|
||||
fn get_language(editor: WeakEntity<Editor>, cx: &mut App) -> Option<Arc<Language>> {
|
||||
|
@ -506,7 +517,7 @@ mod tests {
|
|||
let snapshot = buffer.read(cx).snapshot();
|
||||
|
||||
// Single-point selection
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(0, 4)..Point::new(0, 4));
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(0, 4)..Point::new(0, 4), cx);
|
||||
let snippets = snippets
|
||||
.into_iter()
|
||||
.map(|range| snapshot.text_for_range(range).collect::<String>())
|
||||
|
@ -514,7 +525,7 @@ mod tests {
|
|||
assert_eq!(snippets, vec!["print(1 + 1)"]);
|
||||
|
||||
// Multi-line selection
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(0, 5)..Point::new(2, 0));
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(0, 5)..Point::new(2, 0), cx);
|
||||
let snippets = snippets
|
||||
.into_iter()
|
||||
.map(|range| snapshot.text_for_range(range).collect::<String>())
|
||||
|
@ -527,7 +538,7 @@ mod tests {
|
|||
);
|
||||
|
||||
// Trimming multiple trailing blank lines
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(0, 5)..Point::new(5, 0));
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(0, 5)..Point::new(5, 0), cx);
|
||||
|
||||
let snippets = snippets
|
||||
.into_iter()
|
||||
|
@ -580,7 +591,7 @@ mod tests {
|
|||
let snapshot = buffer.read(cx).snapshot();
|
||||
|
||||
// Jupytext snippet surrounding an empty selection
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(2, 5)..Point::new(2, 5));
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(2, 5)..Point::new(2, 5), cx);
|
||||
|
||||
let snippets = snippets
|
||||
.into_iter()
|
||||
|
@ -596,7 +607,7 @@ mod tests {
|
|||
);
|
||||
|
||||
// Jupytext snippets intersecting a non-empty selection
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(2, 5)..Point::new(6, 2));
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(2, 5)..Point::new(6, 2), cx);
|
||||
let snippets = snippets
|
||||
.into_iter()
|
||||
.map(|range| snapshot.text_for_range(range).collect::<String>())
|
||||
|
@ -623,6 +634,49 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_markdown_code_blocks(cx: &mut App) {
|
||||
use crate::kernels::LocalKernelSpecification;
|
||||
use jupyter_protocol::JupyterKernelspec;
|
||||
|
||||
// Initialize settings
|
||||
settings::init(cx);
|
||||
editor::init(cx);
|
||||
|
||||
// Initialize the ReplStore with a fake filesystem
|
||||
let fs = Arc::new(project::RealFs::new(None, cx.background_executor().clone()));
|
||||
ReplStore::init(fs, cx);
|
||||
|
||||
// Add mock kernel specifications for TypeScript and Python
|
||||
let store = ReplStore::global(cx);
|
||||
store.update(cx, |store, cx| {
|
||||
let typescript_spec = KernelSpecification::Jupyter(LocalKernelSpecification {
|
||||
name: "typescript".into(),
|
||||
kernelspec: JupyterKernelspec {
|
||||
argv: vec![],
|
||||
display_name: "TypeScript".into(),
|
||||
language: "typescript".into(),
|
||||
interrupt_mode: None,
|
||||
metadata: None,
|
||||
env: None,
|
||||
},
|
||||
path: std::path::PathBuf::new(),
|
||||
});
|
||||
|
||||
let python_spec = KernelSpecification::Jupyter(LocalKernelSpecification {
|
||||
name: "python".into(),
|
||||
kernelspec: JupyterKernelspec {
|
||||
argv: vec![],
|
||||
display_name: "Python".into(),
|
||||
language: "python".into(),
|
||||
interrupt_mode: None,
|
||||
metadata: None,
|
||||
env: None,
|
||||
},
|
||||
path: std::path::PathBuf::new(),
|
||||
});
|
||||
|
||||
store.set_kernel_specs_for_testing(vec![typescript_spec, python_spec], cx);
|
||||
});
|
||||
|
||||
let markdown = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
|
||||
let typescript = languages::language(
|
||||
"typescript",
|
||||
|
@ -658,7 +712,7 @@ mod tests {
|
|||
});
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(3, 5)..Point::new(8, 5));
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(3, 5)..Point::new(8, 5), cx);
|
||||
let snippets = snippets
|
||||
.into_iter()
|
||||
.map(|range| snapshot.text_for_range(range).collect::<String>())
|
||||
|
@ -703,7 +757,7 @@ mod tests {
|
|||
});
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(3, 5)..Point::new(12, 5));
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(3, 5)..Point::new(12, 5), cx);
|
||||
let snippets = snippets
|
||||
.into_iter()
|
||||
.map(|range| snapshot.text_for_range(range).collect::<String>())
|
||||
|
@ -742,7 +796,7 @@ mod tests {
|
|||
});
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(4, 5)..Point::new(5, 5));
|
||||
let (snippets, _) = runnable_ranges(&snapshot, Point::new(4, 5)..Point::new(5, 5), cx);
|
||||
let snippets = snippets
|
||||
.into_iter()
|
||||
.map(|range| snapshot.text_for_range(range).collect::<String>())
|
||||
|
|
|
@ -279,4 +279,14 @@ impl ReplStore {
|
|||
pub fn remove_session(&mut self, entity_id: EntityId) {
|
||||
self.sessions.remove(&entity_id);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn set_kernel_specs_for_testing(
|
||||
&mut self,
|
||||
specs: Vec<KernelSpecification>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.kernel_specifications = specs;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue