repl: Support kernel language aliases in REPL (#49762)
Kyle Kelley
created
Add a `kernel_language_names` field to `LanguageConfig` that allows
languages to declare alternative names that Jupyter kernels may use.
This fixes REPL matching for cases where a kernel reports a different
language identifier than Zed's language name.
For example, the Nu extension would set `kernel_language_names =
["nushell", "nu"]` in its config.toml, enabling REPL support for
nu-jupyter-kernel which reports `"language": "nushell"` in its
kernelspec.
The change consolidates kernel language matching logic into a single
`Language::matches_kernel_language()` method that checks the code fence
block name, language name, and the new aliases list (all
case-insensitive).
- [x] Done a self-review taking into account security and performance
aspects
Release Notes:
- Added `kernel_language_names` field for extensions to self identify
REPL mappings
@@ -835,6 +835,11 @@ pub struct LanguageConfig {
pub name: LanguageName,
/// The name of this language for a Markdown code fence block
pub code_fence_block_name: Option<Arc<str>>,
+ /// Alternative language names that Jupyter kernels may report for this language.
+ /// Used when a kernel's `language` field differs from Zed's language name.
+ /// For example, the Nu extension would set this to `["nushell"]`.
+ #[serde(default)]
+ pub kernel_language_names: Vec<Arc<str>>,
// The name of the grammar in a WASM bundle (experimental).
pub grammar: Option<Arc<str>>,
/// The criteria for matching this language to a given file.
@@ -1141,6 +1146,7 @@ impl Default for LanguageConfig {
Self {
name: LanguageName::new_static(""),
code_fence_block_name: None,
+ kernel_language_names: Default::default(),
grammar: None,
matcher: LanguageMatcher::default(),
brackets: Default::default(),
@@ -2075,6 +2081,23 @@ impl Language {
.unwrap_or_else(|| self.config.name.as_ref().to_lowercase().into())
}
+ pub fn matches_kernel_language(&self, kernel_language: &str) -> bool {
+ let kernel_language_lower = kernel_language.to_lowercase();
+
+ if self.code_fence_block_name().to_lowercase() == kernel_language_lower {
+ return true;
+ }
+
+ if self.config.name.as_ref().to_lowercase() == kernel_language_lower {
+ return true;
+ }
+
+ self.config
+ .kernel_language_names
+ .iter()
+ .any(|name| name.to_lowercase() == kernel_language_lower)
+ }
+
pub fn context_provider(&self) -> Option<Arc<dyn ContextProvider>> {
self.context_provider.clone()
}
@@ -636,12 +636,9 @@ 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()- })
+ store_read
+ .pure_jupyter_kernel_specifications()
+ .any(|spec| language.matches_kernel_language(spec.language().as_ref()))
}
fn get_language(editor: WeakEntity<Editor>, cx: &mut App) -> Option<Arc<Language>> {