diff --git a/crates/repl/src/repl_editor.rs b/crates/repl/src/repl_editor.rs index bdb35c5c61..32b59d639d 100644 --- a/crates/repl/src/repl_editor.rs +++ b/crates/repl/src/repl_editor.rs @@ -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, 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, + cx: &mut App, ) -> (Vec>, Option) { 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) -> Vec> { +fn markdown_code_blocks( + buffer: &BufferSnapshot, + range: Range, + cx: &mut App, +) -> Vec> { 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) -> bool { - match language.name().as_ref() { - "TypeScript" | "Python" => true, - _ => false, - } +fn language_supported(language: &Arc, 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, cx: &mut App) -> Option> { @@ -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::()) @@ -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::()) @@ -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::()) @@ -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::()) @@ -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::()) @@ -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::()) diff --git a/crates/repl/src/repl_store.rs b/crates/repl/src/repl_store.rs index 781bc4002f..1a3c9fa49a 100644 --- a/crates/repl/src/repl_store.rs +++ b/crates/repl/src/repl_store.rs @@ -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, + cx: &mut Context, + ) { + self.kernel_specifications = specs; + cx.notify(); + } }