Skip to main content

fresh_languages/
lib.rs

1use std::path::Path;
2
3// Re-export tree-sitter crates for use by fresh-editor
4pub use tree_sitter;
5pub use tree_sitter_highlight;
6pub use tree_sitter_highlight::HighlightConfiguration;
7
8// Re-export language crates (gated by features)
9#[cfg(feature = "tree-sitter-bash")]
10pub use tree_sitter_bash;
11#[cfg(feature = "tree-sitter-c")]
12pub use tree_sitter_c;
13#[cfg(feature = "tree-sitter-c-sharp")]
14pub use tree_sitter_c_sharp;
15#[cfg(feature = "tree-sitter-cpp")]
16pub use tree_sitter_cpp;
17#[cfg(feature = "tree-sitter-css")]
18pub use tree_sitter_css;
19#[cfg(feature = "tree-sitter-go")]
20pub use tree_sitter_go;
21#[cfg(feature = "tree-sitter-html")]
22pub use tree_sitter_html;
23#[cfg(feature = "tree-sitter-java")]
24pub use tree_sitter_java;
25#[cfg(feature = "tree-sitter-javascript")]
26pub use tree_sitter_javascript;
27#[cfg(feature = "tree-sitter-json")]
28pub use tree_sitter_json;
29#[cfg(feature = "tree-sitter-lua")]
30pub use tree_sitter_lua;
31#[cfg(feature = "tree-sitter-odin")]
32pub use tree_sitter_odin;
33#[cfg(feature = "tree-sitter-pascal")]
34pub use tree_sitter_pascal;
35#[cfg(feature = "tree-sitter-php")]
36pub use tree_sitter_php;
37#[cfg(feature = "tree-sitter-python")]
38pub use tree_sitter_python;
39#[cfg(feature = "tree-sitter-ruby")]
40pub use tree_sitter_ruby;
41#[cfg(feature = "tree-sitter-rust")]
42pub use tree_sitter_rust;
43#[cfg(feature = "tree-sitter-typescript")]
44pub use tree_sitter_typescript;
45
46/// Highlight category names used for default languages.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum HighlightCategory {
49    Attribute,
50    Comment,
51    Constant,
52    Function,
53    Keyword,
54    Number,
55    Operator,
56    Property,
57    String,
58    Type,
59    Variable,
60}
61
62impl HighlightCategory {
63    /// Map a default language highlight index to a category
64    pub fn from_default_index(index: usize) -> Option<Self> {
65        match index {
66            0 => Some(Self::Attribute),
67            1 => Some(Self::Comment),
68            2 => Some(Self::Constant),
69            3 => Some(Self::Function),
70            4 => Some(Self::Keyword),
71            5 => Some(Self::Number),
72            6 => Some(Self::Operator),
73            7 => Some(Self::Property),
74            8 => Some(Self::String),
75            9 => Some(Self::Type),
76            10 => Some(Self::Variable),
77            _ => None,
78        }
79    }
80
81    /// Map a TypeScript highlight index to a category.
82    pub fn from_typescript_index(index: usize) -> Option<Self> {
83        match index {
84            0 => Some(Self::Attribute), // attribute
85            1 => Some(Self::Comment),   // comment
86            2 => Some(Self::Constant),  // constant
87            3 => Some(Self::Constant),  // constant.builtin
88            4 => Some(Self::Type),      // constructor
89            5 => Some(Self::String),    // embedded (template substitutions)
90            6 => Some(Self::Function),  // function
91            7 => Some(Self::Function),  // function.builtin
92            8 => Some(Self::Function),  // function.method
93            9 => Some(Self::Keyword),   // keyword
94            10 => Some(Self::Number),   // number
95            11 => Some(Self::Operator), // operator
96            12 => Some(Self::Property), // property
97            13 => Some(Self::Operator), // punctuation.bracket
98            14 => Some(Self::Operator), // punctuation.delimiter
99            15 => Some(Self::Constant), // punctuation.special (template ${})
100            16 => Some(Self::String),   // string
101            17 => Some(Self::String),   // string.special (regex)
102            18 => Some(Self::Type),     // type
103            19 => Some(Self::Type),     // type.builtin
104            20 => Some(Self::Variable), // variable
105            21 => Some(Self::Constant), // variable.builtin (this, super, arguments)
106            22 => Some(Self::Variable), // variable.parameter
107            _ => None,
108        }
109    }
110}
111
112/// Language configuration for syntax highlighting
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum Language {
115    Rust,
116    Python,
117    JavaScript,
118    TypeScript,
119    HTML,
120    CSS,
121    C,
122    Cpp,
123    Go,
124    Json,
125    Java,
126    CSharp,
127    Php,
128    Ruby,
129    Bash,
130    Lua,
131    Pascal,
132    Odin,
133}
134
135impl Language {
136    /// Detect language from file extension
137    pub fn from_path(path: &Path) -> Option<Self> {
138        match path.extension()?.to_str()? {
139            "rs" => Some(Language::Rust),
140            "py" => Some(Language::Python),
141            "js" | "jsx" => Some(Language::JavaScript),
142            "ts" | "tsx" => Some(Language::TypeScript),
143            "html" => Some(Language::HTML),
144            "css" => Some(Language::CSS),
145            "c" | "h" => Some(Language::C),
146            "cpp" | "hpp" | "cc" | "hh" | "cxx" | "hxx" | "cppm" | "ixx" => Some(Language::Cpp),
147            "go" => Some(Language::Go),
148            "json" => Some(Language::Json),
149            "java" => Some(Language::Java),
150            "cs" => Some(Language::CSharp),
151            "php" => Some(Language::Php),
152            "rb" => Some(Language::Ruby),
153            "sh" | "bash" => Some(Language::Bash),
154            "lua" => Some(Language::Lua),
155            "pas" | "p" => Some(Language::Pascal),
156            "odin" => Some(Language::Odin),
157            _ => None,
158        }
159    }
160
161    /// Get tree-sitter highlight configuration for this language
162    pub fn highlight_config(&self) -> Result<HighlightConfiguration, String> {
163        match self {
164            Self::Rust => {
165                #[cfg(feature = "tree-sitter-rust")]
166                {
167                    let mut config = HighlightConfiguration::new(
168                        tree_sitter_rust::LANGUAGE.into(),
169                        "rust",
170                        tree_sitter_rust::HIGHLIGHTS_QUERY,
171                        "",
172                        "",
173                    )
174                    .map_err(|e| format!("Failed to create Rust highlight config: {e}"))?;
175                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
176                    Ok(config)
177                }
178                #[cfg(not(feature = "tree-sitter-rust"))]
179                Err("Rust language support not enabled".to_string())
180            }
181            Self::Python => {
182                #[cfg(feature = "tree-sitter-python")]
183                {
184                    let mut config = HighlightConfiguration::new(
185                        tree_sitter_python::LANGUAGE.into(),
186                        "python",
187                        tree_sitter_python::HIGHLIGHTS_QUERY,
188                        "",
189                        "",
190                    )
191                    .map_err(|e| format!("Failed to create Python highlight config: {e}"))?;
192                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
193                    Ok(config)
194                }
195                #[cfg(not(feature = "tree-sitter-python"))]
196                Err("Python language support not enabled".to_string())
197            }
198            Self::JavaScript => {
199                #[cfg(feature = "tree-sitter-javascript")]
200                {
201                    let mut config = HighlightConfiguration::new(
202                        tree_sitter_javascript::LANGUAGE.into(),
203                        "javascript",
204                        tree_sitter_javascript::HIGHLIGHT_QUERY,
205                        "",
206                        "",
207                    )
208                    .map_err(|e| format!("Failed to create JavaScript highlight config: {e}"))?;
209                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
210                    Ok(config)
211                }
212                #[cfg(not(feature = "tree-sitter-javascript"))]
213                Err("JavaScript language support not enabled".to_string())
214            }
215            Self::TypeScript => {
216                #[cfg(all(feature = "tree-sitter-typescript", feature = "tree-sitter-javascript"))]
217                {
218                    let combined_highlights = format!(
219                        "{}\n{}",
220                        tree_sitter_typescript::HIGHLIGHTS_QUERY,
221                        tree_sitter_javascript::HIGHLIGHT_QUERY
222                    );
223                    let mut config = HighlightConfiguration::new(
224                        tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
225                        "typescript",
226                        &combined_highlights,
227                        "",
228                        tree_sitter_typescript::LOCALS_QUERY,
229                    )
230                    .map_err(|e| format!("Failed to create TypeScript highlight config: {e}"))?;
231                    config.configure(TYPESCRIPT_HIGHLIGHT_CAPTURES);
232                    Ok(config)
233                }
234                #[cfg(not(all(
235                    feature = "tree-sitter-typescript",
236                    feature = "tree-sitter-javascript"
237                )))]
238                Err("TypeScript language support not enabled".to_string())
239            }
240            Self::HTML => {
241                #[cfg(feature = "tree-sitter-html")]
242                {
243                    let mut config = HighlightConfiguration::new(
244                        tree_sitter_html::LANGUAGE.into(),
245                        "html",
246                        tree_sitter_html::HIGHLIGHTS_QUERY,
247                        "",
248                        "",
249                    )
250                    .map_err(|e| format!("Failed to create HTML highlight config: {e}"))?;
251                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
252                    Ok(config)
253                }
254                #[cfg(not(feature = "tree-sitter-html"))]
255                Err("HTML language support not enabled".to_string())
256            }
257            Self::CSS => {
258                #[cfg(feature = "tree-sitter-css")]
259                {
260                    let mut config = HighlightConfiguration::new(
261                        tree_sitter_css::LANGUAGE.into(),
262                        "css",
263                        tree_sitter_css::HIGHLIGHTS_QUERY,
264                        "",
265                        "",
266                    )
267                    .map_err(|e| format!("Failed to create CSS highlight config: {e}"))?;
268                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
269                    Ok(config)
270                }
271                #[cfg(not(feature = "tree-sitter-css"))]
272                Err("CSS language support not enabled".to_string())
273            }
274            Self::C => {
275                #[cfg(feature = "tree-sitter-c")]
276                {
277                    let mut config = HighlightConfiguration::new(
278                        tree_sitter_c::LANGUAGE.into(),
279                        "c",
280                        tree_sitter_c::HIGHLIGHT_QUERY,
281                        "",
282                        "",
283                    )
284                    .map_err(|e| format!("Failed to create C highlight config: {e}"))?;
285                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
286                    Ok(config)
287                }
288                #[cfg(not(feature = "tree-sitter-c"))]
289                Err("C language support not enabled".to_string())
290            }
291            Self::Cpp => {
292                #[cfg(feature = "tree-sitter-cpp")]
293                {
294                    let mut config = HighlightConfiguration::new(
295                        tree_sitter_cpp::LANGUAGE.into(),
296                        "cpp",
297                        tree_sitter_cpp::HIGHLIGHT_QUERY,
298                        "",
299                        "",
300                    )
301                    .map_err(|e| format!("Failed to create C++ highlight config: {e}"))?;
302                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
303                    Ok(config)
304                }
305                #[cfg(not(feature = "tree-sitter-cpp"))]
306                Err("C++ language support not enabled".to_string())
307            }
308            Self::Go => {
309                #[cfg(feature = "tree-sitter-go")]
310                {
311                    let mut config = HighlightConfiguration::new(
312                        tree_sitter_go::LANGUAGE.into(),
313                        "go",
314                        tree_sitter_go::HIGHLIGHTS_QUERY,
315                        "",
316                        "",
317                    )
318                    .map_err(|e| format!("Failed to create Go highlight config: {e}"))?;
319                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
320                    Ok(config)
321                }
322                #[cfg(not(feature = "tree-sitter-go"))]
323                Err("Go language support not enabled".to_string())
324            }
325            Self::Json => {
326                #[cfg(feature = "tree-sitter-json")]
327                {
328                    let mut config = HighlightConfiguration::new(
329                        tree_sitter_json::LANGUAGE.into(),
330                        "json",
331                        tree_sitter_json::HIGHLIGHTS_QUERY,
332                        "",
333                        "",
334                    )
335                    .map_err(|e| format!("Failed to create JSON highlight config: {e}"))?;
336                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
337                    Ok(config)
338                }
339                #[cfg(not(feature = "tree-sitter-json"))]
340                Err("JSON language support not enabled".to_string())
341            }
342            Self::Java => {
343                #[cfg(feature = "tree-sitter-java")]
344                {
345                    let mut config = HighlightConfiguration::new(
346                        tree_sitter_java::LANGUAGE.into(),
347                        "java",
348                        tree_sitter_java::HIGHLIGHTS_QUERY,
349                        "",
350                        "",
351                    )
352                    .map_err(|e| format!("Failed to create Java highlight config: {e}"))?;
353                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
354                    Ok(config)
355                }
356                #[cfg(not(feature = "tree-sitter-java"))]
357                Err("Java language support not enabled".to_string())
358            }
359            Self::CSharp => {
360                #[cfg(feature = "tree-sitter-c-sharp")]
361                {
362                    let mut config = HighlightConfiguration::new(
363                        tree_sitter_c_sharp::LANGUAGE.into(),
364                        "c_sharp",
365                        "",
366                        "",
367                        "",
368                    )
369                    .map_err(|e| format!("Failed to create C# highlight config: {e}"))?;
370                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
371                    Ok(config)
372                }
373                #[cfg(not(feature = "tree-sitter-c-sharp"))]
374                Err("C# language support not enabled".to_string())
375            }
376            Self::Php => {
377                #[cfg(feature = "tree-sitter-php")]
378                {
379                    let mut config = HighlightConfiguration::new(
380                        tree_sitter_php::LANGUAGE_PHP.into(),
381                        "php",
382                        tree_sitter_php::HIGHLIGHTS_QUERY,
383                        "",
384                        "",
385                    )
386                    .map_err(|e| format!("Failed to create PHP highlight config: {e}"))?;
387                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
388                    Ok(config)
389                }
390                #[cfg(not(feature = "tree-sitter-php"))]
391                Err("PHP language support not enabled".to_string())
392            }
393            Self::Ruby => {
394                #[cfg(feature = "tree-sitter-ruby")]
395                {
396                    let mut config = HighlightConfiguration::new(
397                        tree_sitter_ruby::LANGUAGE.into(),
398                        "ruby",
399                        tree_sitter_ruby::HIGHLIGHTS_QUERY,
400                        "",
401                        "",
402                    )
403                    .map_err(|e| format!("Failed to create Ruby highlight config: {e}"))?;
404                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
405                    Ok(config)
406                }
407                #[cfg(not(feature = "tree-sitter-ruby"))]
408                Err("Ruby language support not enabled".to_string())
409            }
410            Self::Bash => {
411                #[cfg(feature = "tree-sitter-bash")]
412                {
413                    let mut config = HighlightConfiguration::new(
414                        tree_sitter_bash::LANGUAGE.into(),
415                        "bash",
416                        tree_sitter_bash::HIGHLIGHT_QUERY,
417                        "",
418                        "",
419                    )
420                    .map_err(|e| format!("Failed to create Bash highlight config: {e}"))?;
421                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
422                    Ok(config)
423                }
424                #[cfg(not(feature = "tree-sitter-bash"))]
425                Err("Bash language support not enabled".to_string())
426            }
427            Self::Lua => {
428                #[cfg(feature = "tree-sitter-lua")]
429                {
430                    let mut config = HighlightConfiguration::new(
431                        tree_sitter_lua::LANGUAGE.into(),
432                        "lua",
433                        tree_sitter_lua::HIGHLIGHTS_QUERY,
434                        "",
435                        "",
436                    )
437                    .map_err(|e| format!("Failed to create Lua highlight config: {e}"))?;
438                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
439                    Ok(config)
440                }
441                #[cfg(not(feature = "tree-sitter-lua"))]
442                Err("Lua language support not enabled".to_string())
443            }
444            Self::Pascal => {
445                #[cfg(feature = "tree-sitter-pascal")]
446                {
447                    let mut config = HighlightConfiguration::new(
448                        tree_sitter_pascal::LANGUAGE.into(),
449                        "pascal",
450                        "",
451                        "",
452                        "",
453                    )
454                    .map_err(|e| format!("Failed to create Pascal highlight config: {e}"))?;
455                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
456                    Ok(config)
457                }
458                #[cfg(not(feature = "tree-sitter-pascal"))]
459                Err("Pascal language support not enabled".to_string())
460            }
461            Self::Odin => {
462                #[cfg(feature = "tree-sitter-odin")]
463                {
464                    let mut config = HighlightConfiguration::new(
465                        tree_sitter_odin::LANGUAGE.into(),
466                        "odin",
467                        "",
468                        "",
469                        "",
470                    )
471                    .map_err(|e| format!("Failed to create Odin highlight config: {e}"))?;
472                    config.configure(DEFAULT_HIGHLIGHT_CAPTURES);
473                    Ok(config)
474                }
475                #[cfg(not(feature = "tree-sitter-odin"))]
476                Err("Odin language support not enabled".to_string())
477            }
478        }
479    }
480
481    /// Map tree-sitter highlight index to a highlight category
482    pub fn highlight_category(&self, index: usize) -> Option<HighlightCategory> {
483        match self {
484            Self::TypeScript => HighlightCategory::from_typescript_index(index),
485            _ => HighlightCategory::from_default_index(index),
486        }
487    }
488}
489
490impl Language {
491    /// Returns all available language variants
492    pub fn all() -> &'static [Language] {
493        &[
494            Language::Rust,
495            Language::Python,
496            Language::JavaScript,
497            Language::TypeScript,
498            Language::HTML,
499            Language::CSS,
500            Language::C,
501            Language::Cpp,
502            Language::Go,
503            Language::Json,
504            Language::Java,
505            Language::CSharp,
506            Language::Php,
507            Language::Ruby,
508            Language::Bash,
509            Language::Lua,
510            Language::Pascal,
511            Language::Odin,
512        ]
513    }
514
515    /// Returns the language ID (lowercase identifier used in config/internal)
516    pub fn id(&self) -> &'static str {
517        match self {
518            Self::Rust => "rust",
519            Self::Python => "python",
520            Self::JavaScript => "javascript",
521            Self::TypeScript => "typescript",
522            Self::HTML => "html",
523            Self::CSS => "css",
524            Self::C => "c",
525            Self::Cpp => "cpp",
526            Self::Go => "go",
527            Self::Json => "json",
528            Self::Java => "java",
529            Self::CSharp => "c_sharp",
530            Self::Php => "php",
531            Self::Ruby => "ruby",
532            Self::Bash => "bash",
533            Self::Lua => "lua",
534            Self::Pascal => "pascal",
535            Self::Odin => "odin",
536        }
537    }
538
539    /// Returns the human-readable display name
540    pub fn display_name(&self) -> &'static str {
541        match self {
542            Self::Rust => "Rust",
543            Self::Python => "Python",
544            Self::JavaScript => "JavaScript",
545            Self::TypeScript => "TypeScript",
546            Self::HTML => "HTML",
547            Self::CSS => "CSS",
548            Self::C => "C",
549            Self::Cpp => "C++",
550            Self::Go => "Go",
551            Self::Json => "JSON",
552            Self::Java => "Java",
553            Self::CSharp => "C#",
554            Self::Php => "PHP",
555            Self::Ruby => "Ruby",
556            Self::Bash => "Bash",
557            Self::Lua => "Lua",
558            Self::Pascal => "Pascal",
559            Self::Odin => "Odin",
560        }
561    }
562
563    /// Parse a language from its ID or display name
564    pub fn from_id(id: &str) -> Option<Self> {
565        let id_lower = id.to_lowercase();
566        match id_lower.as_str() {
567            "rust" => Some(Self::Rust),
568            "python" => Some(Self::Python),
569            "javascript" => Some(Self::JavaScript),
570            "typescript" => Some(Self::TypeScript),
571            "html" => Some(Self::HTML),
572            "css" => Some(Self::CSS),
573            "c" => Some(Self::C),
574            "cpp" | "c++" => Some(Self::Cpp),
575            "go" => Some(Self::Go),
576            "json" => Some(Self::Json),
577            "java" => Some(Self::Java),
578            "c_sharp" | "c#" | "csharp" => Some(Self::CSharp),
579            "php" => Some(Self::Php),
580            "ruby" => Some(Self::Ruby),
581            "bash" => Some(Self::Bash),
582            "lua" => Some(Self::Lua),
583            "pascal" => Some(Self::Pascal),
584            "odin" => Some(Self::Odin),
585            _ => None,
586        }
587    }
588
589    /// Try to map a syntect syntax name to a tree-sitter Language.
590    ///
591    /// This is used to get tree-sitter features (indentation, semantic highlighting)
592    /// when using a syntect grammar for syntax highlighting. This is best-effort since
593    /// tree-sitter only supports ~18 languages while syntect supports 100+.
594    ///
595    /// Syntect uses names like "Rust", "Python", "JavaScript", "JSON", "C++", "C#",
596    /// "Bourne Again Shell (bash)", etc.
597    pub fn from_name(name: &str) -> Option<Self> {
598        // First try exact display name match
599        for lang in Self::all() {
600            if lang.display_name() == name {
601                return Some(*lang);
602            }
603        }
604
605        // Then try case-insensitive matching and common aliases
606        let name_lower = name.to_lowercase();
607        match name_lower.as_str() {
608            "rust" => Some(Self::Rust),
609            "python" => Some(Self::Python),
610            "javascript" | "javascript (babel)" => Some(Self::JavaScript),
611            "typescript" | "typescriptreact" => Some(Self::TypeScript),
612            "html" => Some(Self::HTML),
613            "css" => Some(Self::CSS),
614            "c" => Some(Self::C),
615            "c++" => Some(Self::Cpp),
616            "go" | "golang" => Some(Self::Go),
617            "json" => Some(Self::Json),
618            "java" => Some(Self::Java),
619            "c#" => Some(Self::CSharp),
620            "php" => Some(Self::Php),
621            "ruby" => Some(Self::Ruby),
622            "lua" => Some(Self::Lua),
623            "pascal" => Some(Self::Pascal),
624            "odin" => Some(Self::Odin),
625            _ => {
626                // Try matching shell variants
627                if name_lower.contains("bash") || name_lower.contains("shell") {
628                    return Some(Self::Bash);
629                }
630                None
631            }
632        }
633    }
634}
635
636impl std::fmt::Display for Language {
637    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
638        write!(f, "{}", self.id())
639    }
640}
641
642const DEFAULT_HIGHLIGHT_CAPTURES: &[&str] = &[
643    "attribute",
644    "comment",
645    "constant",
646    "function",
647    "keyword",
648    "number",
649    "operator",
650    "property",
651    "string",
652    "type",
653    "variable",
654];
655
656const TYPESCRIPT_HIGHLIGHT_CAPTURES: &[&str] = &[
657    "attribute",
658    "comment",
659    "constant",
660    "constant.builtin",
661    "constructor",
662    "embedded",
663    "function",
664    "function.builtin",
665    "function.method",
666    "keyword",
667    "number",
668    "operator",
669    "property",
670    "punctuation.bracket",
671    "punctuation.delimiter",
672    "punctuation.special",
673    "string",
674    "string.special",
675    "type",
676    "type.builtin",
677    "variable",
678    "variable.builtin",
679    "variable.parameter",
680];