From 89c210c4ca770240e8ade18cda749cc09be49663 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 12 Jun 2025 19:45:29 +0800 Subject: [PATCH] highlighter: Avoid crash when language not registered. (#954) Close #952 Fallback to use plain text to display. --- crates/story/examples/code-editor.rs | 7 ++- crates/ui/src/highlighter/highlighter.rs | 37 +++++++----- crates/ui/src/highlighter/languages.rs | 71 ++++++++++++------------ 3 files changed, 64 insertions(+), 51 deletions(-) diff --git a/crates/story/examples/code-editor.rs b/crates/story/examples/code-editor.rs index 4f60daff..d370901e 100644 --- a/crates/story/examples/code-editor.rs +++ b/crates/story/examples/code-editor.rs @@ -118,11 +118,12 @@ impl Example { &language_state, |this, state, _: &DropdownEvent>, cx| { if let Some(val) = state.read(cx).selected_value() { - if let Some(language) = Language::from_str(&val) { - this.language = Lang::BuiltIn(language); - } else { + if val == "navi" { this.language = Lang::External("navi"); + } else { + this.language = Lang::BuiltIn(Language::from_str(&val)); } + this.need_update = true; cx.notify(); } diff --git a/crates/ui/src/highlighter/highlighter.rs b/crates/ui/src/highlighter/highlighter.rs index b4898523..ba174589 100644 --- a/crates/ui/src/highlighter/highlighter.rs +++ b/crates/ui/src/highlighter/highlighter.rs @@ -1,5 +1,6 @@ use super::HighlightTheme; use crate::highlighter::LanguageRegistry; +use anyhow::{anyhow, Context, Result}; use gpui::{App, HighlightStyle, SharedString}; use indexset::BTreeMap; use std::{ @@ -44,21 +45,34 @@ pub struct SyntaxHighlighter { impl SyntaxHighlighter { /// Create a new SyntaxHighlighter for HTML. pub fn new(lang: &str, cx: &App) -> Self { - Self::build_combined_injections_query(&lang, cx).unwrap_or_else(|| panic!( - "failed to build language {}, please make sure have registered the language in LanguageRegistry", - lang - )) + match Self::build_combined_injections_query(&lang, cx) { + Ok(result) => result, + Err(err) => { + tracing::warn!( + "SyntaxHighlighter init failed, fallback to use `text`, {}", + err + ); + Self::build_combined_injections_query("text", cx).unwrap() + } + } } /// Build the combined injections query for the given language. /// /// https://github.com/tree-sitter/tree-sitter/blob/v0.25.5/highlight/src/lib.rs#L336 - fn build_combined_injections_query(lang: &str, cx: &App) -> Option { + fn build_combined_injections_query(lang: &str, cx: &App) -> Result { let registry = LanguageRegistry::global(cx); - let config = registry.language(&lang)?; + let Some(config) = registry.language(&lang) else { + return Err(anyhow!( + "language {:?} is not registered in `LanguageRegistry`", + lang + )); + }; let mut parser = Parser::new(); - _ = parser.set_language(&config.language); + parser + .set_language(&config.language) + .context("parse set_language")?; // Concatenate the query strings, keeping track of the start offset of each section. let mut query_source = String::new(); @@ -70,12 +84,7 @@ impl SyntaxHighlighter { // Construct a single query by concatenating the three query strings, but record the // range of pattern indices that belong to each individual string. - let query = match Query::new(&config.language, &query_source) { - Ok(query) => Some(query), - Err(err) => { - panic!("failed create Query for language {}, err: {}", lang, err); - } - }?; + let query = Query::new(&config.language, &query_source).context("new query")?; let mut locals_pattern_index = 0; let mut highlights_pattern_index = 0; @@ -164,7 +173,7 @@ impl SyntaxHighlighter { // let highlight_indices = vec![None; query.capture_names().len()]; - Some(Self { + Ok(Self { language: config.name.clone(), query: Some(query), injection_queries, diff --git a/crates/ui/src/highlighter/languages.rs b/crates/ui/src/highlighter/languages.rs index 5d24ebdd..f44b2686 100644 --- a/crates/ui/src/highlighter/languages.rs +++ b/crates/ui/src/highlighter/languages.rs @@ -2,6 +2,7 @@ use gpui::SharedString; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, enum_iterator::Sequence)] pub enum Language { + Plain, Bash, C, CMake, @@ -78,6 +79,7 @@ impl Language { pub fn name(&self) -> &'static str { match self { + Self::Plain => "text", Self::Bash => "bash", Self::C => "c", Self::CMake => "cmake", @@ -113,41 +115,41 @@ impl Language { } } - pub fn from_str(s: &str) -> Option { + pub fn from_str(s: &str) -> Self { match s { - "bash" | "sh" => Some(Self::Bash), - "c" => Some(Self::C), - "cmake" => Some(Self::CMake), - "cpp" | "c++" => Some(Self::Cpp), - "csharp" | "cs" => Some(Self::CSharp), - "css" | "scss" => Some(Self::Css), - "diff" => Some(Self::Diff), - "ejs" => Some(Self::Ejs), - "elixir" | "ex" => Some(Self::Elixir), - "erb" => Some(Self::Erb), - "go" => Some(Self::Go), - "graphql" => Some(Self::GraphQL), - "html" => Some(Self::Html), - "java" => Some(Self::Java), - "javascript" | "js" => Some(Self::JavaScript), - "jsdoc" => Some(Self::JsDoc), - "json" | "jsonc" => Some(Self::Json), - "make" | "makefile" => Some(Self::Make), - "markdown" | "md" | "mdx" => Some(Self::Markdown), - "markdown_inline" | "markdown-inline" => Some(Self::MarkdownInline), - "proto" | "protobuf" => Some(Self::Proto), - "python" | "py" => Some(Self::Python), - "ruby" | "rb" => Some(Self::Ruby), - "rust" | "rs" => Some(Self::Rust), - "scala" => Some(Self::Scala), - "sql" => Some(Self::Sql), - "swift" => Some(Self::Swift), - "toml" => Some(Self::Toml), - "tsx" => Some(Self::Tsx), - "typescript" | "ts" => Some(Self::TypeScript), - "yaml" | "yml" => Some(Self::Yaml), - "zig" => Some(Self::Zig), - _ => None, + "bash" | "sh" => Self::Bash, + "c" => Self::C, + "cmake" => Self::CMake, + "cpp" | "c++" => Self::Cpp, + "csharp" | "cs" => Self::CSharp, + "css" | "scss" => Self::Css, + "diff" => Self::Diff, + "ejs" => Self::Ejs, + "elixir" | "ex" => Self::Elixir, + "erb" => Self::Erb, + "go" => Self::Go, + "graphql" => Self::GraphQL, + "html" => Self::Html, + "java" => Self::Java, + "javascript" | "js" => Self::JavaScript, + "jsdoc" => Self::JsDoc, + "json" | "jsonc" => Self::Json, + "make" | "makefile" => Self::Make, + "markdown" | "md" | "mdx" => Self::Markdown, + "markdown_inline" | "markdown-inline" => Self::MarkdownInline, + "proto" | "protobuf" => Self::Proto, + "python" | "py" => Self::Python, + "ruby" | "rb" => Self::Ruby, + "rust" | "rs" => Self::Rust, + "scala" => Self::Scala, + "sql" => Self::Sql, + "swift" => Self::Swift, + "toml" => Self::Toml, + "tsx" => Self::Tsx, + "typescript" | "ts" => Self::TypeScript, + "yaml" | "yml" => Self::Yaml, + "zig" => Self::Zig, + _ => Self::Plain, } } @@ -182,6 +184,7 @@ impl Language { /// (language, query, injection, locals) pub(super) fn config(&self) -> LanguageConfig { let (language, query, injection, locals) = match self { + Self::Plain => (tree_sitter_json::LANGUAGE, "", "", ""), Self::Json => ( tree_sitter_json::LANGUAGE, include_str!("languages/json/highlights.scm"),