@@ -0,0 +1,117 @@
1+use crate::{Range, Rope, Selection, Tendril, Transaction};
2+use smallvec::SmallVec;
3+
4+const PAIRS: &[(char, char)] = &[
5+ ('(', ')'),
6+ ('{', '}'),
7+ ('[', ']'),
8+ ('\'', '\''),
9+ ('"', '"'),
10+ ('`', '`'),
11+];
12+
13+const CLOSE_BEFORE: &str = ")]}'\":;> \n"; // includes space and newline
14+
15+// insert hook:
16+// Fn(doc, selection, char) => Option<Transaction>
17+// problem is, we want to do this per range, so we can call default handler for some ranges
18+// so maybe ret Vec<Option<Change>>
19+// but we also need to be able to return transactions...
20+//
21+// to simplify, maybe return Option<Transaction> and just reimplement the default
22+
23+// TODO: delete implementation where it erases the whole bracket (|) -> |
24+
25+pub fn hook(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
26+ for &(open, close) in PAIRS {
27+ if open == ch {
28+ let t = if open == close {
29+ return None;
30+ // handle_same()
31+ } else {
32+ handle_open(doc, selection, open, close, CLOSE_BEFORE)
33+ };
34+ return Some(t);
35+ }
36+
37+ if close == ch {
38+ // && char_at pos == close
39+ return Some(handle_close(doc, selection, open, close));
40+ }
41+ }
42+
43+ None
44+}
45+
46+// TODO: special handling for lifetimes in rust: if preceeded by & or < don't auto close '
47+// for example "&'a mut", or "fn<'a>"
48+
49+fn next_char(doc: &Rope, pos: usize) -> Option<char> {
50+ if pos >= doc.len_chars() {
51+ return None;
52+ }
53+ Some(doc.char(pos))
54+}
55+
56+// TODO: if not cursor but selection, wrap on both sides of selection (surround)
57+fn handle_open(
58+ doc: &Rope,
59+ selection: &Selection,
60+ open: char,
61+ close: char,
62+ close_before: &str,
63+) -> Transaction {
64+ let mut ranges = SmallVec::with_capacity(selection.len());
65+
66+ let mut transaction = Transaction::change_by_selection(doc, selection, |range| {
67+ let pos = range.head;
68+ let next = next_char(doc, pos);
69+
70+ ranges.push(Range::new(range.anchor, pos + 1)); // pos + open
71+
72+ match next {
73+ Some(ch) if !close_before.contains(ch) => {
74+ // TODO: else return (use default handler that inserts open)
75+ (pos, pos, Some(Tendril::from_char(open)))
76+ }
77+ // None | Some(ch) if close_before.contains(ch) => {}
78+ _ => {
79+ // insert open & close
80+ let mut pair = Tendril::with_capacity(2);
81+ pair.push_char(open);
82+ pair.push_char(close);
83+
84+ (pos, pos, Some(pair))
85+ }
86+ }
87+ });
88+
89+ transaction.with_selection(Selection::new(ranges, selection.primary_index()))
90+}
91+
92+fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) -> Transaction {
93+ let mut ranges = SmallVec::with_capacity(selection.len());
94+
95+ let mut transaction = Transaction::change_by_selection(doc, selection, |range| {
96+ let pos = range.head;
97+ let next = next_char(doc, pos);
98+
99+ ranges.push(Range::new(range.anchor, pos + 1)); // pos + close
100+
101+ if next == Some(close) {
102+ // return transaction that moves past close
103+ (pos, pos, None) // no-op
104+ } else {
105+ // TODO: else return (use default handler that inserts close)
106+ (pos, pos, Some(Tendril::from_char(close)))
107+ }
108+ });
109+
110+ transaction.with_selection(Selection::new(ranges, selection.primary_index()))
111+}
112+
113+// handle cases where open and close is the same, or in triples ("""docstring""")
114+fn handle_same() {
115+ // if not cursor but selection, wrap
116+ // let next = next char
117+}