Skip to main content

gpui_component/highlighter/
wasm_stub.rs

1//! WASM stub implementation for highlighter module.
2//! Provides empty/no-op implementations since tree-sitter is not available in WASM.
3//!
4//! Note: diagnostics.rs is available in WASM, only syntax highlighting requires stubs.
5
6use gpui::{HighlightStyle, SharedString};
7use std::ops::Range;
8use std::time::Duration;
9
10// Syntax highlighter stub
11pub struct SyntaxHighlighter;
12
13impl SyntaxHighlighter {
14    pub fn new(_language: impl AsRef<str>) -> Self {
15        Self
16    }
17
18    pub fn highlight(&self, _text: &ropey::Rope) -> Vec<(Range<usize>, HighlightStyle)> {
19        Vec::new()
20    }
21
22    pub fn styles(
23        &self,
24        range: &Range<usize>,
25        _theme: &HighlightTheme,
26    ) -> Vec<(Range<usize>, HighlightStyle)> {
27        // If the matched styles is empty, return a default range.
28        vec![(range.clone(), HighlightStyle::default())]
29    }
30
31    pub fn update(
32        &mut self,
33        _edit: Option<crate::input::InputEdit>,
34        _text: &ropey::Rope,
35        _timeout: Option<Duration>,
36    ) -> bool {
37        // No-op in WASM
38        true
39    }
40
41    pub fn edit_tree(&mut self, _edit: Option<crate::input::InputEdit>, _text: &ropey::Rope) {
42        // No-op in WASM
43    }
44
45    pub fn language(&self) -> &SharedString {
46        static EMPTY: SharedString = SharedString::new_static("");
47        &EMPTY
48    }
49
50    pub fn text(&self) -> &ropey::Rope {
51        static EMPTY_ROPE: LazyLock<ropey::Rope> = LazyLock::new(ropey::Rope::new);
52        &EMPTY_ROPE
53    }
54
55    pub fn tree(&self) -> Option<&crate::input::Tree> {
56        None
57    }
58}
59
60// Language enum stub
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub enum Language {
63    Unknown,
64}
65
66impl Language {
67    pub fn from_str(_name: &str) -> Self {
68        Language::Unknown
69    }
70
71    pub fn name(&self) -> &'static str {
72        "unknown"
73    }
74
75    pub fn config(&self) -> LanguageConfig {
76        LanguageConfig {
77            name: "unknown".into(),
78        }
79    }
80
81    pub fn all() -> impl Iterator<Item = Self> {
82        std::iter::once(Language::Unknown)
83    }
84}
85
86// Language config stub (without tree_sitter::Language)
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct LanguageConfig {
89    pub name: SharedString,
90}
91
92impl LanguageConfig {
93    pub fn has_grammar(&self) -> bool {
94        false
95    }
96}
97
98// Re-export theme types from registry module (which will be conditionally compiled)
99// For WASM, we create minimal stubs here
100use schemars::JsonSchema;
101use serde::{Deserialize, Serialize};
102use serde_repr::{Deserialize_repr, Serialize_repr};
103use std::{
104    collections::HashMap,
105    sync::{LazyLock, Mutex},
106};
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
109#[serde(rename_all = "lowercase")]
110pub enum FontStyle {
111    Normal,
112    Italic,
113    Underline,
114}
115
116#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, JsonSchema, Serialize_repr, Deserialize_repr)]
117#[repr(u16)]
118pub enum FontWeightContent {
119    Thin = 100,
120    ExtraLight = 200,
121    Light = 300,
122    Normal = 400,
123    Medium = 500,
124    Semibold = 600,
125    Bold = 700,
126    ExtraBold = 800,
127    Black = 900,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
131pub struct ThemeStyle {
132    pub color: Option<gpui::Hsla>,
133    pub font_style: Option<FontStyle>,
134    pub font_weight: Option<FontWeightContent>,
135}
136
137impl From<ThemeStyle> for HighlightStyle {
138    fn from(style: ThemeStyle) -> Self {
139        HighlightStyle {
140            color: style.color,
141            font_weight: style.font_weight.map(|w| match w {
142                FontWeightContent::Thin => gpui::FontWeight::THIN,
143                FontWeightContent::ExtraLight => gpui::FontWeight::EXTRA_LIGHT,
144                FontWeightContent::Light => gpui::FontWeight::LIGHT,
145                FontWeightContent::Normal => gpui::FontWeight::NORMAL,
146                FontWeightContent::Medium => gpui::FontWeight::MEDIUM,
147                FontWeightContent::Semibold => gpui::FontWeight::SEMIBOLD,
148                FontWeightContent::Bold => gpui::FontWeight::BOLD,
149                FontWeightContent::ExtraBold => gpui::FontWeight::EXTRA_BOLD,
150                FontWeightContent::Black => gpui::FontWeight::BLACK,
151            }),
152            font_style: style.font_style.map(|s| match s {
153                FontStyle::Normal => gpui::FontStyle::Normal,
154                FontStyle::Italic => gpui::FontStyle::Italic,
155                FontStyle::Underline => gpui::FontStyle::Normal,
156            }),
157            ..Default::default()
158        }
159    }
160}
161
162#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
163pub struct SyntaxColors {
164    pub attribute: Option<ThemeStyle>,
165    pub boolean: Option<ThemeStyle>,
166    pub comment: Option<ThemeStyle>,
167    pub comment_doc: Option<ThemeStyle>,
168    pub constant: Option<ThemeStyle>,
169    pub constructor: Option<ThemeStyle>,
170    pub embedded: Option<ThemeStyle>,
171    pub emphasis: Option<ThemeStyle>,
172    #[serde(rename = "emphasis.strong")]
173    pub emphasis_strong: Option<ThemeStyle>,
174    #[serde(rename = "enum")]
175    pub enum_: Option<ThemeStyle>,
176    pub function: Option<ThemeStyle>,
177    pub hint: Option<ThemeStyle>,
178    pub keyword: Option<ThemeStyle>,
179    pub label: Option<ThemeStyle>,
180    #[serde(rename = "link_text")]
181    pub link_text: Option<ThemeStyle>,
182    #[serde(rename = "link_uri")]
183    pub link_uri: Option<ThemeStyle>,
184    pub number: Option<ThemeStyle>,
185    pub operator: Option<ThemeStyle>,
186    pub predictive: Option<ThemeStyle>,
187    pub preproc: Option<ThemeStyle>,
188    pub primary: Option<ThemeStyle>,
189    pub property: Option<ThemeStyle>,
190    pub punctuation: Option<ThemeStyle>,
191    #[serde(rename = "punctuation.bracket")]
192    pub punctuation_bracket: Option<ThemeStyle>,
193    #[serde(rename = "punctuation.delimiter")]
194    pub punctuation_delimiter: Option<ThemeStyle>,
195    #[serde(rename = "punctuation.list_marker")]
196    pub punctuation_list_marker: Option<ThemeStyle>,
197    #[serde(rename = "punctuation.special")]
198    pub punctuation_special: Option<ThemeStyle>,
199    pub string: Option<ThemeStyle>,
200    #[serde(rename = "string.escape")]
201    pub string_escape: Option<ThemeStyle>,
202    #[serde(rename = "string.regex")]
203    pub string_regex: Option<ThemeStyle>,
204    #[serde(rename = "string.special")]
205    pub string_special: Option<ThemeStyle>,
206    #[serde(rename = "string.special.symbol")]
207    pub string_special_symbol: Option<ThemeStyle>,
208    pub tag: Option<ThemeStyle>,
209    #[serde(rename = "tag.doctype")]
210    pub tag_doctype: Option<ThemeStyle>,
211    #[serde(rename = "text.code.span")]
212    pub text_code_span: Option<ThemeStyle>,
213    #[serde(rename = "text.literal")]
214    pub text_literal: Option<ThemeStyle>,
215    pub title: Option<ThemeStyle>,
216    #[serde(rename = "type")]
217    pub type_: Option<ThemeStyle>,
218    pub variable: Option<ThemeStyle>,
219    #[serde(rename = "variable.special")]
220    pub variable_special: Option<ThemeStyle>,
221    pub variant: Option<ThemeStyle>,
222}
223
224impl SyntaxColors {
225    pub fn style(&self, name: &str) -> Option<HighlightStyle> {
226        if name.is_empty() {
227            return None;
228        }
229
230        let style = match name {
231            "attribute" => self.attribute,
232            "boolean" => self.boolean,
233            "comment" => self.comment,
234            "comment.doc" => self.comment_doc,
235            "constant" => self.constant,
236            "constructor" => self.constructor,
237            "embedded" => self.embedded,
238            "emphasis" => self.emphasis,
239            "emphasis.strong" => self.emphasis_strong,
240            "enum" => self.enum_,
241            "function" => self.function,
242            "hint" => self.hint,
243            "keyword" => self.keyword,
244            "label" => self.label,
245            "link_text" => self.link_text,
246            "link_uri" => self.link_uri,
247            "number" => self.number,
248            "operator" => self.operator,
249            "predictive" => self.predictive,
250            "preproc" => self.preproc,
251            "primary" => self.primary,
252            "property" => self.property,
253            "punctuation" => self.punctuation,
254            "punctuation.bracket" => self.punctuation_bracket,
255            "punctuation.delimiter" => self.punctuation_delimiter,
256            "punctuation.list_marker" => self.punctuation_list_marker,
257            "punctuation.special" => self.punctuation_special,
258            "string" => self.string,
259            "string.escape" => self.string_escape,
260            "string.regex" => self.string_regex,
261            "string.special" => self.string_special,
262            "string.special.symbol" => self.string_special_symbol,
263            "tag" => self.tag,
264            "tag.doctype" => self.tag_doctype,
265            "text.code.span" => self.text_code_span,
266            "text.literal" => self.text_literal,
267            "title" => self.title,
268            "type" => self.type_,
269            "variable" => self.variable,
270            "variable.special" => self.variable_special,
271            "variant" => self.variant,
272            _ => None,
273        }
274        .map(|s| s.into());
275
276        if style.is_some() {
277            style
278        } else if name.contains('.') {
279            name.split('.').next().and_then(|prefix| self.style(prefix))
280        } else {
281            None
282        }
283    }
284
285    pub fn style_for_index(&self, index: usize) -> Option<HighlightStyle> {
286        const HIGHLIGHT_NAMES: [&str; 41] = [
287            "attribute",
288            "boolean",
289            "comment",
290            "comment.doc",
291            "constant",
292            "constructor",
293            "embedded",
294            "emphasis",
295            "emphasis.strong",
296            "enum",
297            "function",
298            "hint",
299            "keyword",
300            "label",
301            "link_text",
302            "link_uri",
303            "number",
304            "operator",
305            "predictive",
306            "preproc",
307            "primary",
308            "property",
309            "punctuation",
310            "punctuation.bracket",
311            "punctuation.delimiter",
312            "punctuation.list_marker",
313            "punctuation.special",
314            "string",
315            "string.escape",
316            "string.regex",
317            "string.special",
318            "string.special.symbol",
319            "tag",
320            "tag.doctype",
321            "text.code.span",
322            "text.literal",
323            "title",
324            "type",
325            "variable",
326            "variable.special",
327            "variant",
328        ];
329
330        HIGHLIGHT_NAMES.get(index).and_then(|name| self.style(name))
331    }
332}
333
334#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
335pub struct StatusColors {
336    // Minimal stub
337}
338
339impl StatusColors {
340    pub fn error(&self, _cx: &gpui::App) -> gpui::Hsla {
341        gpui::Hsla::default()
342    }
343
344    pub fn error_background(&self, _cx: &gpui::App) -> gpui::Hsla {
345        gpui::Hsla::default()
346    }
347
348    pub fn error_border(&self, _cx: &gpui::App) -> gpui::Hsla {
349        gpui::Hsla::default()
350    }
351
352    pub fn warning(&self, _cx: &gpui::App) -> gpui::Hsla {
353        gpui::Hsla::default()
354    }
355
356    pub fn warning_background(&self, _cx: &gpui::App) -> gpui::Hsla {
357        gpui::Hsla::default()
358    }
359
360    pub fn warning_border(&self, _cx: &gpui::App) -> gpui::Hsla {
361        gpui::Hsla::default()
362    }
363
364    pub fn info(&self, _cx: &gpui::App) -> gpui::Hsla {
365        gpui::Hsla::default()
366    }
367
368    pub fn info_background(&self, _cx: &gpui::App) -> gpui::Hsla {
369        gpui::Hsla::default()
370    }
371
372    pub fn info_border(&self, _cx: &gpui::App) -> gpui::Hsla {
373        gpui::Hsla::default()
374    }
375
376    pub fn success(&self, _cx: &gpui::App) -> gpui::Hsla {
377        gpui::Hsla::default()
378    }
379
380    pub fn success_background(&self, _cx: &gpui::App) -> gpui::Hsla {
381        gpui::Hsla::default()
382    }
383
384    pub fn success_border(&self, _cx: &gpui::App) -> gpui::Hsla {
385        gpui::Hsla::default()
386    }
387
388    pub fn hint(&self, _cx: &gpui::App) -> gpui::Hsla {
389        gpui::Hsla::default()
390    }
391
392    pub fn hint_background(&self, _cx: &gpui::App) -> gpui::Hsla {
393        gpui::Hsla::default()
394    }
395
396    pub fn hint_border(&self, _cx: &gpui::App) -> gpui::Hsla {
397        gpui::Hsla::default()
398    }
399}
400
401#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
402pub struct HighlightThemeStyle {
403    pub editor_background: Option<gpui::Hsla>,
404    pub editor_foreground: Option<gpui::Hsla>,
405    pub editor_active_line: Option<gpui::Hsla>,
406    pub editor_line_number: Option<gpui::Hsla>,
407    pub editor_active_line_number: Option<gpui::Hsla>,
408    pub editor_invisible: Option<gpui::Hsla>,
409    #[serde(rename = "editor.gutter.background")]
410    pub editor_gutter_background: Option<gpui::Hsla>,
411    #[serde(flatten)]
412    pub status: StatusColors,
413    #[serde(rename = "syntax")]
414    pub syntax: SyntaxColors,
415}
416
417#[derive(Debug, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
418pub struct HighlightTheme {
419    pub name: String,
420    #[serde(default)]
421    pub appearance: crate::ThemeMode,
422    pub style: HighlightThemeStyle,
423}
424
425impl std::ops::Deref for HighlightTheme {
426    type Target = SyntaxColors;
427
428    fn deref(&self) -> &Self::Target {
429        &self.style.syntax
430    }
431}
432
433impl HighlightTheme {
434    pub fn default_dark() -> std::sync::Arc<Self> {
435        use crate::DEFAULT_THEME_COLORS;
436        DEFAULT_THEME_COLORS[&crate::ThemeMode::Dark].1.clone()
437    }
438
439    pub fn default_light() -> std::sync::Arc<Self> {
440        use crate::DEFAULT_THEME_COLORS;
441        DEFAULT_THEME_COLORS[&crate::ThemeMode::Light].1.clone()
442    }
443}
444
445impl gpui_base::input::HighlightStyleResolver for HighlightTheme {
446    fn style(&self, name: &str) -> Option<HighlightStyle> {
447        self.style.syntax.style(name)
448    }
449}
450
451// Language registry stub
452pub struct LanguageRegistry {
453    languages: Mutex<HashMap<SharedString, LanguageConfig>>,
454}
455
456impl LanguageRegistry {
457    pub fn singleton() -> &'static LazyLock<LanguageRegistry> {
458        static INSTANCE: LazyLock<LanguageRegistry> = LazyLock::new(|| LanguageRegistry {
459            languages: Mutex::new(HashMap::new()),
460        });
461        &INSTANCE
462    }
463
464    pub fn register(&self, lang: &str, config: &LanguageConfig) {
465        self.languages
466            .lock()
467            .unwrap()
468            .insert(lang.to_string().into(), config.clone());
469    }
470
471    pub fn languages(&self) -> Vec<SharedString> {
472        self.languages.lock().unwrap().keys().cloned().collect()
473    }
474
475    pub fn language(&self, name: &str) -> Option<LanguageConfig> {
476        self.languages.lock().unwrap().get(name).cloned()
477    }
478}