gpui_base/input/editor/
language_config.rs1use gpui::SharedString;
4use regex::Regex;
5use std::sync::Arc;
6
7use super::SyntaxContext;
8
9#[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#[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#[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#[derive(Debug, Clone)]
79#[non_exhaustive]
80pub struct LanguageConfig {
81 pub brackets: Vec<BracketPair>,
82 pub auto_closing_pairs: Option<Vec<AutoClosingPair>>,
83 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}