mirror of https://github.com/helix-editor/helix
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
5.0 KiB
Rust
164 lines
5.0 KiB
Rust
//! When typing the opening character of one of the possible pairs defined below,
|
|
//! this module provides the functionality to insert the paired closing character.
|
|
|
|
use crate::{Range, Rope, Selection, Tendril, Transaction};
|
|
use smallvec::SmallVec;
|
|
|
|
// Heavily based on https://github.com/codemirror/closebrackets/
|
|
|
|
pub const PAIRS: &[(char, char)] = &[
|
|
('(', ')'),
|
|
('{', '}'),
|
|
('[', ']'),
|
|
('\'', '\''),
|
|
('"', '"'),
|
|
('`', '`'),
|
|
];
|
|
|
|
const CLOSE_BEFORE: &str = ")]}'\":;> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}"; // includes space and newlines
|
|
|
|
// insert hook:
|
|
// Fn(doc, selection, char) => Option<Transaction>
|
|
// problem is, we want to do this per range, so we can call default handler for some ranges
|
|
// so maybe ret Vec<Option<Change>>
|
|
// but we also need to be able to return transactions...
|
|
//
|
|
// to simplify, maybe return Option<Transaction> and just reimplement the default
|
|
|
|
// TODO: delete implementation where it erases the whole bracket (|) -> |
|
|
|
|
#[must_use]
|
|
pub fn hook(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
|
|
for &(open, close) in PAIRS {
|
|
if open == ch {
|
|
if open == close {
|
|
return handle_same(doc, selection, open);
|
|
} else {
|
|
return Some(handle_open(doc, selection, open, close, CLOSE_BEFORE));
|
|
}
|
|
}
|
|
|
|
if close == ch {
|
|
// && char_at pos == close
|
|
return Some(handle_close(doc, selection, open, close));
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
// TODO: special handling for lifetimes in rust: if preceeded by & or < don't auto close '
|
|
// for example "&'a mut", or "fn<'a>"
|
|
|
|
fn next_char(doc: &Rope, pos: usize) -> Option<char> {
|
|
if pos >= doc.len_chars() {
|
|
return None;
|
|
}
|
|
Some(doc.char(pos))
|
|
}
|
|
// TODO: selections should be extended if range, moved if point.
|
|
|
|
// TODO: if not cursor but selection, wrap on both sides of selection (surround)
|
|
fn handle_open(
|
|
doc: &Rope,
|
|
selection: &Selection,
|
|
open: char,
|
|
close: char,
|
|
close_before: &str,
|
|
) -> Transaction {
|
|
let mut ranges = SmallVec::with_capacity(selection.len());
|
|
|
|
let mut offs = 0;
|
|
|
|
let transaction = Transaction::change_by_selection(doc, selection, |range| {
|
|
let pos = range.head;
|
|
let next = next_char(doc, pos);
|
|
|
|
let head = pos + offs + open.len_utf8();
|
|
// if selection, retain anchor, if cursor, move over
|
|
ranges.push(Range::new(
|
|
if range.is_empty() {
|
|
head
|
|
} else {
|
|
range.anchor + offs
|
|
},
|
|
head,
|
|
));
|
|
|
|
match next {
|
|
Some(ch) if !close_before.contains(ch) => {
|
|
offs += 1;
|
|
// TODO: else return (use default handler that inserts open)
|
|
(pos, pos, Some(Tendril::from_char(open)))
|
|
}
|
|
// None | Some(ch) if close_before.contains(ch) => {}
|
|
_ => {
|
|
// insert open & close
|
|
let mut pair = Tendril::with_capacity(2);
|
|
pair.push_char(open);
|
|
pair.push_char(close);
|
|
|
|
offs += 2;
|
|
|
|
(pos, pos, Some(pair))
|
|
}
|
|
}
|
|
});
|
|
|
|
transaction.with_selection(Selection::new(ranges, selection.primary_index()))
|
|
}
|
|
|
|
fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) -> Transaction {
|
|
let mut ranges = SmallVec::with_capacity(selection.len());
|
|
|
|
let mut offs = 0;
|
|
|
|
let transaction = Transaction::change_by_selection(doc, selection, |range| {
|
|
let pos = range.head;
|
|
let next = next_char(doc, pos);
|
|
|
|
let head = pos + offs + close.len_utf8();
|
|
// if selection, retain anchor, if cursor, move over
|
|
ranges.push(Range::new(
|
|
if range.is_empty() {
|
|
head
|
|
} else {
|
|
range.anchor + offs
|
|
},
|
|
head,
|
|
));
|
|
|
|
if next == Some(close) {
|
|
// return transaction that moves past close
|
|
(pos, pos, None) // no-op
|
|
} else {
|
|
offs += close.len_utf8();
|
|
|
|
// TODO: else return (use default handler that inserts close)
|
|
(pos, pos, Some(Tendril::from_char(close)))
|
|
}
|
|
});
|
|
|
|
transaction.with_selection(Selection::new(ranges, selection.primary_index()))
|
|
}
|
|
|
|
// handle cases where open and close is the same, or in triples ("""docstring""")
|
|
fn handle_same(_doc: &Rope, _selection: &Selection, _token: char) -> Option<Transaction> {
|
|
// if not cursor but selection, wrap
|
|
// let next = next char
|
|
|
|
// if next == bracket {
|
|
// // if start of syntax node, insert token twice (new pair because node is complete)
|
|
// // elseif colsedBracketAt
|
|
// // is_triple == allow triple && next 3 is equal
|
|
// // cursor jump over
|
|
// }
|
|
//} else if allow_triple && followed by triple {
|
|
//}
|
|
//} else if next != word char && prev != bracket && prev != word char {
|
|
// // condition checks for cases like I' where you don't want I'' (or I'm)
|
|
// insert pair ("")
|
|
//}
|
|
None
|
|
}
|