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,
|this, state, _: &DropdownEvent<Vec<SharedString>>, 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();
}

View file

@ -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<Self> {
fn build_combined_injections_query(lang: &str, cx: &App) -> Result<Self> {
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,

View file

@ -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<Self> {
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"),