highlighter: Avoid crash when language not registered. (#954)

Close #952 

Fallback to use plain text to display.
This commit is contained in:
Jason Lee 2025-06-12 19:45:29 +08:00 committed by GitHub
parent 1032881103
commit 89c210c4ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 64 additions and 51 deletions

View file

@ -118,11 +118,12 @@ impl Example {
&language_state, &language_state,
|this, state, _: &DropdownEvent<Vec<SharedString>>, cx| { |this, state, _: &DropdownEvent<Vec<SharedString>>, cx| {
if let Some(val) = state.read(cx).selected_value() { if let Some(val) = state.read(cx).selected_value() {
if let Some(language) = Language::from_str(&val) { if val == "navi" {
this.language = Lang::BuiltIn(language);
} else {
this.language = Lang::External("navi"); this.language = Lang::External("navi");
} else {
this.language = Lang::BuiltIn(Language::from_str(&val));
} }
this.need_update = true; this.need_update = true;
cx.notify(); cx.notify();
} }

View file

@ -1,5 +1,6 @@
use super::HighlightTheme; use super::HighlightTheme;
use crate::highlighter::LanguageRegistry; use crate::highlighter::LanguageRegistry;
use anyhow::{anyhow, Context, Result};
use gpui::{App, HighlightStyle, SharedString}; use gpui::{App, HighlightStyle, SharedString};
use indexset::BTreeMap; use indexset::BTreeMap;
use std::{ use std::{
@ -44,21 +45,34 @@ pub struct SyntaxHighlighter {
impl SyntaxHighlighter { impl SyntaxHighlighter {
/// Create a new SyntaxHighlighter for HTML. /// Create a new SyntaxHighlighter for HTML.
pub fn new(lang: &str, cx: &App) -> Self { pub fn new(lang: &str, cx: &App) -> Self {
Self::build_combined_injections_query(&lang, cx).unwrap_or_else(|| panic!( match Self::build_combined_injections_query(&lang, cx) {
"failed to build language {}, please make sure have registered the language in LanguageRegistry", Ok(result) => result,
lang 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. /// 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 /// 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<Self> { fn build_combined_injections_query(lang: &str, cx: &App) -> Result<Self> {
let registry = LanguageRegistry::global(cx); 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(); 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. // Concatenate the query strings, keeping track of the start offset of each section.
let mut query_source = String::new(); 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 // Construct a single query by concatenating the three query strings, but record the
// range of pattern indices that belong to each individual string. // range of pattern indices that belong to each individual string.
let query = match Query::new(&config.language, &query_source) { let query = Query::new(&config.language, &query_source).context("new query")?;
Ok(query) => Some(query),
Err(err) => {
panic!("failed create Query for language {}, err: {}", lang, err);
}
}?;
let mut locals_pattern_index = 0; let mut locals_pattern_index = 0;
let mut highlights_pattern_index = 0; let mut highlights_pattern_index = 0;
@ -164,7 +173,7 @@ impl SyntaxHighlighter {
// let highlight_indices = vec![None; query.capture_names().len()]; // let highlight_indices = vec![None; query.capture_names().len()];
Some(Self { Ok(Self {
language: config.name.clone(), language: config.name.clone(),
query: Some(query), query: Some(query),
injection_queries, injection_queries,

View file

@ -2,6 +2,7 @@ use gpui::SharedString;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, enum_iterator::Sequence)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, enum_iterator::Sequence)]
pub enum Language { pub enum Language {
Plain,
Bash, Bash,
C, C,
CMake, CMake,
@ -78,6 +79,7 @@ impl Language {
pub fn name(&self) -> &'static str { pub fn name(&self) -> &'static str {
match self { match self {
Self::Plain => "text",
Self::Bash => "bash", Self::Bash => "bash",
Self::C => "c", Self::C => "c",
Self::CMake => "cmake", Self::CMake => "cmake",
@ -113,41 +115,41 @@ impl Language {
} }
} }
pub fn from_str(s: &str) -> Option<Self> { pub fn from_str(s: &str) -> Self {
match s { match s {
"bash" | "sh" => Some(Self::Bash), "bash" | "sh" => Self::Bash,
"c" => Some(Self::C), "c" => Self::C,
"cmake" => Some(Self::CMake), "cmake" => Self::CMake,
"cpp" | "c++" => Some(Self::Cpp), "cpp" | "c++" => Self::Cpp,
"csharp" | "cs" => Some(Self::CSharp), "csharp" | "cs" => Self::CSharp,
"css" | "scss" => Some(Self::Css), "css" | "scss" => Self::Css,
"diff" => Some(Self::Diff), "diff" => Self::Diff,
"ejs" => Some(Self::Ejs), "ejs" => Self::Ejs,
"elixir" | "ex" => Some(Self::Elixir), "elixir" | "ex" => Self::Elixir,
"erb" => Some(Self::Erb), "erb" => Self::Erb,
"go" => Some(Self::Go), "go" => Self::Go,
"graphql" => Some(Self::GraphQL), "graphql" => Self::GraphQL,
"html" => Some(Self::Html), "html" => Self::Html,
"java" => Some(Self::Java), "java" => Self::Java,
"javascript" | "js" => Some(Self::JavaScript), "javascript" | "js" => Self::JavaScript,
"jsdoc" => Some(Self::JsDoc), "jsdoc" => Self::JsDoc,
"json" | "jsonc" => Some(Self::Json), "json" | "jsonc" => Self::Json,
"make" | "makefile" => Some(Self::Make), "make" | "makefile" => Self::Make,
"markdown" | "md" | "mdx" => Some(Self::Markdown), "markdown" | "md" | "mdx" => Self::Markdown,
"markdown_inline" | "markdown-inline" => Some(Self::MarkdownInline), "markdown_inline" | "markdown-inline" => Self::MarkdownInline,
"proto" | "protobuf" => Some(Self::Proto), "proto" | "protobuf" => Self::Proto,
"python" | "py" => Some(Self::Python), "python" | "py" => Self::Python,
"ruby" | "rb" => Some(Self::Ruby), "ruby" | "rb" => Self::Ruby,
"rust" | "rs" => Some(Self::Rust), "rust" | "rs" => Self::Rust,
"scala" => Some(Self::Scala), "scala" => Self::Scala,
"sql" => Some(Self::Sql), "sql" => Self::Sql,
"swift" => Some(Self::Swift), "swift" => Self::Swift,
"toml" => Some(Self::Toml), "toml" => Self::Toml,
"tsx" => Some(Self::Tsx), "tsx" => Self::Tsx,
"typescript" | "ts" => Some(Self::TypeScript), "typescript" | "ts" => Self::TypeScript,
"yaml" | "yml" => Some(Self::Yaml), "yaml" | "yml" => Self::Yaml,
"zig" => Some(Self::Zig), "zig" => Self::Zig,
_ => None, _ => Self::Plain,
} }
} }
@ -182,6 +184,7 @@ impl Language {
/// (language, query, injection, locals) /// (language, query, injection, locals)
pub(super) fn config(&self) -> LanguageConfig { pub(super) fn config(&self) -> LanguageConfig {
let (language, query, injection, locals) = match self { let (language, query, injection, locals) = match self {
Self::Plain => (tree_sitter_json::LANGUAGE, "", "", ""),
Self::Json => ( Self::Json => (
tree_sitter_json::LANGUAGE, tree_sitter_json::LANGUAGE,
include_str!("languages/json/highlights.scm"), include_str!("languages/json/highlights.scm"),