1use crate::config::CompletionType;
4use std::borrow::Cow::{self, Borrowed, Owned};
5use std::cell::Cell;
6
7#[derive(Copy, Clone, Debug, Eq, PartialEq)]
10pub enum CmdKind {
11 MoveCursor,
13 Other,
15 ForcedRefresh,
18}
19
20pub trait Highlighter {
25 fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
31 let _ = pos;
32 Borrowed(line)
33 }
34 fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
37 &'s self,
38 prompt: &'p str,
39 default: bool,
40 ) -> Cow<'b, str> {
41 let _ = default;
42 Borrowed(prompt)
43 }
44 fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
47 Borrowed(hint)
48 }
49 fn highlight_candidate<'c>(
54 &self,
55 candidate: &'c str, completion: CompletionType,
57 ) -> Cow<'c, str> {
58 let _ = completion;
59 Borrowed(candidate)
60 }
61 fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool {
67 let _ = (line, pos, kind);
68 false
69 }
70}
71
72impl Highlighter for () {}
73
74#[derive(Default)]
78pub struct MatchingBracketHighlighter {
79 bracket: Cell<Option<(u8, usize)>>, }
81
82impl MatchingBracketHighlighter {
83 #[must_use]
85 pub fn new() -> Self {
86 Self {
87 bracket: Cell::new(None),
88 }
89 }
90}
91
92impl Highlighter for MatchingBracketHighlighter {
93 fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
94 if line.len() <= 1 {
95 return Borrowed(line);
96 }
97 if let Some((bracket, pos)) = self.bracket.get() {
99 if let Some((matching, idx)) = find_matching_bracket(line, pos, bracket) {
100 let mut copy = line.to_owned();
101 copy.replace_range(idx..=idx, &format!("\x1b[1;34m{}\x1b[0m", matching as char));
102 return Owned(copy);
103 }
104 }
105 Borrowed(line)
106 }
107
108 fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool {
109 if kind == CmdKind::ForcedRefresh {
110 self.bracket.set(None);
111 return false;
112 }
113 self.bracket.set(check_bracket(line, pos));
115 self.bracket.get().is_some()
116 }
117}
118
119fn find_matching_bracket(line: &str, pos: usize, bracket: u8) -> Option<(u8, usize)> {
120 let matching = matching_bracket(bracket);
121 let mut idx;
122 let mut unmatched = 1;
123 if is_open_bracket(bracket) {
124 idx = pos + 1;
126 let bytes = &line.as_bytes()[idx..];
127 for b in bytes {
128 if *b == matching {
129 unmatched -= 1;
130 if unmatched == 0 {
131 debug_assert_eq!(matching, line.as_bytes()[idx]);
132 return Some((matching, idx));
133 }
134 } else if *b == bracket {
135 unmatched += 1;
136 }
137 idx += 1;
138 }
139 debug_assert_eq!(idx, line.len());
140 } else {
141 idx = pos;
143 let bytes = &line.as_bytes()[..idx];
144 for b in bytes.iter().rev() {
145 if *b == matching {
146 unmatched -= 1;
147 if unmatched == 0 {
148 debug_assert_eq!(matching, line.as_bytes()[idx - 1]);
149 return Some((matching, idx - 1));
150 }
151 } else if *b == bracket {
152 unmatched += 1;
153 }
154 idx -= 1;
155 }
156 debug_assert_eq!(idx, 0);
157 }
158 None
159}
160
161fn check_bracket(line: &str, pos: usize) -> Option<(u8, usize)> {
163 if line.is_empty() {
164 return None;
165 }
166 let mut pos = pos;
167 if pos >= line.len() {
168 pos = line.len() - 1; let b = line.as_bytes()[pos]; if is_close_bracket(b) {
171 Some((b, pos))
172 } else {
173 None
174 }
175 } else {
176 let mut under_cursor = true;
177 loop {
178 let b = line.as_bytes()[pos];
179 if is_close_bracket(b) {
180 return if pos == 0 { None } else { Some((b, pos)) };
181 } else if is_open_bracket(b) {
182 return if pos + 1 == line.len() {
183 None
184 } else {
185 Some((b, pos))
186 };
187 } else if under_cursor && pos > 0 {
188 under_cursor = false;
189 pos -= 1; } else {
191 return None;
192 }
193 }
194 }
195}
196
197const fn matching_bracket(bracket: u8) -> u8 {
198 match bracket {
199 b'{' => b'}',
200 b'}' => b'{',
201 b'[' => b']',
202 b']' => b'[',
203 b'(' => b')',
204 b')' => b'(',
205 b => b,
206 }
207}
208const fn is_open_bracket(bracket: u8) -> bool {
209 matches!(bracket, b'{' | b'[' | b'(')
210}
211const fn is_close_bracket(bracket: u8) -> bool {
212 matches!(bracket, b'}' | b']' | b')')
213}
214
215#[cfg(test)]
216mod tests {
217 #[test]
218 pub fn find_matching_bracket() {
219 use super::find_matching_bracket;
220 assert_eq!(find_matching_bracket("(...", 0, b'('), None);
221 assert_eq!(find_matching_bracket("...)", 3, b')'), None);
222
223 assert_eq!(find_matching_bracket("()..", 0, b'('), Some((b')', 1)));
224 assert_eq!(find_matching_bracket("(..)", 0, b'('), Some((b')', 3)));
225
226 assert_eq!(find_matching_bracket("..()", 3, b')'), Some((b'(', 2)));
227 assert_eq!(find_matching_bracket("(..)", 3, b')'), Some((b'(', 0)));
228
229 assert_eq!(find_matching_bracket("(())", 0, b'('), Some((b')', 3)));
230 assert_eq!(find_matching_bracket("(())", 3, b')'), Some((b'(', 0)));
231 }
232 #[test]
233 pub fn check_bracket() {
234 use super::check_bracket;
235 assert_eq!(check_bracket(")...", 0), None);
236 assert_eq!(check_bracket("(...", 2), None);
237 assert_eq!(check_bracket("...(", 3), None);
238 assert_eq!(check_bracket("...(", 4), None);
239 assert_eq!(check_bracket("..).", 4), None);
240
241 assert_eq!(check_bracket("(...", 0), Some((b'(', 0)));
242 assert_eq!(check_bracket("(...", 1), Some((b'(', 0)));
243 assert_eq!(check_bracket("...)", 3), Some((b')', 3)));
244 assert_eq!(check_bracket("...)", 4), Some((b')', 3)));
245 }
246 #[test]
247 pub fn matching_bracket() {
248 use super::matching_bracket;
249 assert_eq!(matching_bracket(b'('), b')');
250 assert_eq!(matching_bracket(b')'), b'(');
251 }
252
253 #[test]
254 pub fn is_open_bracket() {
255 use super::is_close_bracket;
256 use super::is_open_bracket;
257 assert!(is_open_bracket(b'('));
258 assert!(is_close_bracket(b')'));
259 }
260}