Skip to main content

gpui_component/input/
language_config.rs

1//! Language editing configuration. Grammar resources live in `highlighter`.
2
3pub use gpui_base::input::language_config::LanguageConfig;
4
5use gpui_base::input::{AutoClosingPair, BracketPair, IndentationRules, SyntaxContext};
6use regex::Regex;
7use std::sync::LazyLock;
8
9struct ComponentLanguages {
10    defaults: std::collections::HashMap<&'static str, std::rc::Rc<LanguageConfig>>,
11    fallback: std::rc::Rc<LanguageConfig>,
12}
13
14impl gpui_base::input::LanguageProvider for ComponentLanguages {
15    fn language_name(&self, name: &str) -> gpui::SharedString {
16        crate::highlighter::LanguageRegistry::singleton().editing_language_name(name)
17    }
18
19    fn config(&self, name: &str) -> std::rc::Rc<LanguageConfig> {
20        self.defaults.get(name).unwrap_or(&self.fallback).clone()
21    }
22
23    fn syntax_context_provider(
24        &self,
25        name: &str,
26    ) -> Option<std::rc::Rc<dyn gpui_base::input::SyntaxContextProvider>> {
27        super::syntax_context::syntax_context_provider(name)
28    }
29}
30
31pub(crate) fn init(cx: &mut gpui::App) {
32    gpui_base::input::set_language_provider(
33        std::rc::Rc::new(ComponentLanguages {
34            defaults: ["text", "json", "python"]
35                .into_iter()
36                .map(|name| (name, std::rc::Rc::new(default_language_config(name))))
37                .collect(),
38            fallback: std::rc::Rc::new(LanguageConfig::default()),
39        }),
40        cx,
41    );
42}
43
44/// Language editing defaults. Editor preferences are stored separately on EditorState.
45/// Unknown languages use structural bracket indentation; only Python treats a colon
46/// as a block opener. Plain text has no structural or automatic pairs.
47fn default_language_config(language: &str) -> LanguageConfig {
48    static PYTHON_INDENT: LazyLock<IndentationRules> = LazyLock::new(|| {
49        IndentationRules::new(
50            Regex::new(r"[\{\(\[:]\s*$").unwrap(),
51            Regex::new(r"^\s*[\}\)\]]").unwrap(),
52        )
53    });
54    match language.to_lowercase().as_str() {
55        "text" => LanguageConfig::default()
56            .brackets([])
57            .auto_closing_pairs([]),
58        "json" => LanguageConfig::default()
59            .brackets([BracketPair::new("{", "}"), BracketPair::new("[", "]")])
60            .auto_closing_pairs(
61                [
62                    AutoClosingPair::new("{", "}"),
63                    AutoClosingPair::new("[", "]"),
64                    AutoClosingPair::new("\"", "\""),
65                ]
66                .into_iter()
67                .map(|p| p.not_in([SyntaxContext::String, SyntaxContext::Comment])),
68            ),
69        "python" => LanguageConfig::default().indentation_rules(PYTHON_INDENT.clone()),
70        _ => LanguageConfig::default(),
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn plain_text_has_no_editing_rules() {
80        let rules = default_language_config("text");
81        assert!(rules.brackets.is_empty());
82        assert!(rules.auto_closing_pairs.unwrap().is_empty());
83        assert!(rules.indentation_rules.is_none());
84    }
85
86    #[test]
87    fn json_only_closes_json_delimiters() {
88        let rules = default_language_config("json");
89        let pairs = rules.auto_closing_pairs.unwrap();
90        assert_eq!(pairs.len(), 3);
91        assert!(
92            pairs
93                .iter()
94                .all(|p| p.open.as_ref() != "'" && p.open.as_ref() != "(")
95        );
96    }
97
98    #[test]
99    fn colon_indentation_is_language_specific() {
100        assert!(
101            default_language_config("python")
102                .indentation_rules
103                .unwrap()
104                .increase_indent_pattern
105                .unwrap()
106                .is_match("if enabled:")
107        );
108        for language in ["rust", "javascript", "json", "unknown"] {
109            assert!(
110                default_language_config(language)
111                    .indentation_rules
112                    .is_none()
113            );
114        }
115    }
116}