Skip to main content

gpui_base/input/editor/
language_config.rs

1//! Language-specific bracket pairing and indentation rules.
2
3use gpui::SharedString;
4use regex::Regex;
5use std::sync::Arc;
6
7use super::SyntaxContext;
8
9/// A structural pair, used for indentation and splitting Enter between delimiters.
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[non_exhaustive]
12pub struct BracketPair {
13    pub open: SharedString,
14    pub close: SharedString,
15}
16
17impl BracketPair {
18    pub fn new(open: impl Into<SharedString>, close: impl Into<SharedString>) -> Self {
19        Self {
20            open: open.into(),
21            close: close.into(),
22        }
23    }
24}
25
26/// An automatic closing pair and the syntax contexts in which insertion is disabled.
27/// Empty delimiters are ignored. Strings support delimiters such as `/*` and `*/`.
28#[derive(Debug, Clone, PartialEq, Eq)]
29#[non_exhaustive]
30pub struct AutoClosingPair {
31    pub open: SharedString,
32    pub close: SharedString,
33    pub not_in: Vec<SyntaxContext>,
34}
35
36impl AutoClosingPair {
37    pub fn new(open: impl Into<SharedString>, close: impl Into<SharedString>) -> Self {
38        Self {
39            open: open.into(),
40            close: close.into(),
41            not_in: Vec::new(),
42        }
43    }
44
45    pub fn not_in(mut self, contexts: impl IntoIterator<Item = SyntaxContext>) -> Self {
46        self.not_in = contexts.into_iter().collect();
47        self
48    }
49}
50
51/// Language indentation patterns, using Rust's `regex` syntax.
52/// Patterns are compiled by the caller, so invalid expressions are reported at setup.
53/// Applied on Enter: increase matches text before the caret; decrease matches text
54/// after it. This does not reformat existing lines or indentation on paste.
55#[derive(Debug, Clone, Default)]
56#[non_exhaustive]
57pub struct IndentationRules {
58    pub increase_indent_pattern: Option<Arc<Regex>>,
59    pub decrease_indent_pattern: Option<Arc<Regex>>,
60}
61
62impl IndentationRules {
63    pub fn new(increase: Regex, decrease: Regex) -> Self {
64        Self {
65            increase_indent_pattern: Some(Arc::new(increase)),
66            decrease_indent_pattern: Some(Arc::new(decrease)),
67        }
68    }
69}
70
71/// Declarative language editing rules, independent of editor preferences and parsers.
72///
73/// Mirrors the supported subset of Monaco's language configuration. `None` for
74/// `auto_closing_pairs` uses `brackets`; `Some(vec![])` disables all automatic pairs.
75/// Use the builders to configure a default value; additional language capabilities
76/// can be added without breaking callers. Tree-sitter queries are configured in
77/// the parser implementation, not in these rules.
78#[derive(Debug, Clone)]
79#[non_exhaustive]
80pub struct LanguageConfig {
81    pub brackets: Vec<BracketPair>,
82    pub auto_closing_pairs: Option<Vec<AutoClosingPair>>,
83    /// Automatic insertion is allowed before these characters, whitespace, or EOF.
84    pub auto_close_before: SharedString,
85    pub indentation_rules: Option<IndentationRules>,
86}
87
88impl Default for LanguageConfig {
89    fn default() -> Self {
90        let brackets = vec![
91            BracketPair::new("(", ")"),
92            BracketPair::new("[", "]"),
93            BracketPair::new("{", "}"),
94        ];
95        let mut pairs: Vec<_> = brackets
96            .iter()
97            .map(|p| {
98                AutoClosingPair::new(p.open.clone(), p.close.clone())
99                    .not_in([SyntaxContext::String, SyntaxContext::Comment])
100            })
101            .collect();
102        pairs.extend(
103            [
104                AutoClosingPair::new("\"", "\""),
105                AutoClosingPair::new("'", "'"),
106            ]
107            .into_iter()
108            .map(|p| p.not_in([SyntaxContext::String, SyntaxContext::Comment])),
109        );
110        Self {
111            brackets,
112            auto_closing_pairs: Some(pairs),
113            auto_close_before: ";:.,=}])>".into(),
114            indentation_rules: None,
115        }
116    }
117}
118
119impl LanguageConfig {
120    pub fn brackets(mut self, pairs: impl IntoIterator<Item = BracketPair>) -> Self {
121        self.brackets = pairs.into_iter().collect();
122        self
123    }
124    pub fn auto_closing_pairs(mut self, pairs: impl IntoIterator<Item = AutoClosingPair>) -> Self {
125        self.auto_closing_pairs = Some(pairs.into_iter().collect());
126        self
127    }
128    pub fn auto_close_before(mut self, characters: impl Into<SharedString>) -> Self {
129        self.auto_close_before = characters.into();
130        self
131    }
132    pub fn indentation_rules(mut self, rules: IndentationRules) -> Self {
133        self.indentation_rules = Some(rules);
134        self
135    }
136
137    pub(crate) fn closing_pairs(&self) -> impl Iterator<Item = (&str, &str, &[SyntaxContext])> {
138        let configured = self
139            .auto_closing_pairs
140            .as_ref()
141            .into_iter()
142            .flatten()
143            .map(|p| (p.open.as_ref(), p.close.as_ref(), p.not_in.as_slice()));
144        let fallback = self
145            .brackets
146            .iter()
147            .filter(|_| self.auto_closing_pairs.is_none())
148            .map(|p| (p.open.as_ref(), p.close.as_ref(), &[][..]));
149        configured
150            .chain(fallback)
151            .filter(|(open, close, _)| !open.is_empty() && !close.is_empty())
152    }
153
154    pub(crate) fn opens_indent(&self, text: &str) -> bool {
155        self.indentation_rules
156            .as_ref()
157            .and_then(|r| r.increase_indent_pattern.as_ref())
158            .map_or_else(
159                || {
160                    self.brackets
161                        .iter()
162                        .any(|p| !p.open.is_empty() && text.trim_end().ends_with(p.open.as_ref()))
163                },
164                |r| r.is_match(text),
165            )
166    }
167
168    pub(crate) fn closes_indent(&self, text: &str) -> bool {
169        self.indentation_rules
170            .as_ref()
171            .and_then(|r| r.decrease_indent_pattern.as_ref())
172            .is_some_and(|r| r.is_match(text))
173    }
174}