mirror of
https://github.com/helix-editor/helix.git
synced 2025-01-19 13:37:06 +04:00
Implement auto-pairs behavior for open and close.
This commit is contained in:
parent
a32806b490
commit
71999cce43
117
helix-core/src/auto_pairs.rs
Normal file
117
helix-core/src/auto_pairs.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
use crate::{Range, Rope, Selection, Tendril, Transaction};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
const PAIRS: &[(char, char)] = &[
|
||||||
|
('(', ')'),
|
||||||
|
('{', '}'),
|
||||||
|
('[', ']'),
|
||||||
|
('\'', '\''),
|
||||||
|
('"', '"'),
|
||||||
|
('`', '`'),
|
||||||
|
];
|
||||||
|
|
||||||
|
const CLOSE_BEFORE: &str = ")]}'\":;> \n"; // includes space and newline
|
||||||
|
|
||||||
|
// 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 (|) -> |
|
||||||
|
|
||||||
|
pub fn hook(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
|
||||||
|
for &(open, close) in PAIRS {
|
||||||
|
if open == ch {
|
||||||
|
let t = if open == close {
|
||||||
|
return None;
|
||||||
|
// handle_same()
|
||||||
|
} else {
|
||||||
|
handle_open(doc, selection, open, close, CLOSE_BEFORE)
|
||||||
|
};
|
||||||
|
return Some(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
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: 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 transaction = Transaction::change_by_selection(doc, selection, |range| {
|
||||||
|
let pos = range.head;
|
||||||
|
let next = next_char(doc, pos);
|
||||||
|
|
||||||
|
ranges.push(Range::new(range.anchor, pos + 1)); // pos + open
|
||||||
|
|
||||||
|
match next {
|
||||||
|
Some(ch) if !close_before.contains(ch) => {
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
(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 transaction = Transaction::change_by_selection(doc, selection, |range| {
|
||||||
|
let pos = range.head;
|
||||||
|
let next = next_char(doc, pos);
|
||||||
|
|
||||||
|
ranges.push(Range::new(range.anchor, pos + 1)); // pos + close
|
||||||
|
|
||||||
|
if next == Some(close) {
|
||||||
|
// return transaction that moves past close
|
||||||
|
(pos, pos, None) // no-op
|
||||||
|
} else {
|
||||||
|
// 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() {
|
||||||
|
// if not cursor but selection, wrap
|
||||||
|
// let next = next char
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
|
pub mod auto_pairs;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod diagnostic;
|
pub mod diagnostic;
|
||||||
pub mod graphemes;
|
pub mod graphemes;
|
||||||
|
@ -180,6 +180,10 @@ pub fn ranges(&self) -> &[Range] {
|
|||||||
&self.ranges
|
&self.ranges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn primary_index(&self) -> usize {
|
||||||
|
self.primary_index
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
/// Constructs a selection holding a single range.
|
/// Constructs a selection holding a single range.
|
||||||
pub fn single(anchor: usize, head: usize) -> Self {
|
pub fn single(anchor: usize, head: usize) -> Self {
|
||||||
|
@ -1009,9 +1009,23 @@ pub fn goto_reference(cx: &mut Context) {
|
|||||||
// NOTE: Transactions in this module get appended to history when we switch back to normal mode.
|
// NOTE: Transactions in this module get appended to history when we switch back to normal mode.
|
||||||
pub mod insert {
|
pub mod insert {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
pub type Hook = fn(&Rope, &Selection, char) -> Option<Transaction>;
|
||||||
|
|
||||||
|
use helix_core::auto_pairs;
|
||||||
|
const HOOKS: &[Hook] = &[auto_pairs::hook];
|
||||||
|
|
||||||
// TODO: insert means add text just before cursor, on exit we should be on the last letter.
|
// TODO: insert means add text just before cursor, on exit we should be on the last letter.
|
||||||
pub fn insert_char(cx: &mut Context, c: char) {
|
pub fn insert_char(cx: &mut Context, c: char) {
|
||||||
let doc = cx.doc();
|
let doc = cx.doc();
|
||||||
|
|
||||||
|
// run through insert hooks, stopping on the first one that returns Some(t)
|
||||||
|
for hook in HOOKS {
|
||||||
|
if let Some(transaction) = hook(doc.text(), doc.selection(), c) {
|
||||||
|
doc.apply(&transaction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let c = Tendril::from_char(c);
|
let c = Tendril::from_char(c);
|
||||||
let transaction = Transaction::insert(doc.text(), doc.selection(), c);
|
let transaction = Transaction::insert(doc.text(), doc.selection(), c);
|
||||||
|
|
||||||
@ -1019,6 +1033,7 @@ pub fn insert_char(cx: &mut Context, c: char) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_tab(cx: &mut Context) {
|
pub fn insert_tab(cx: &mut Context) {
|
||||||
|
// TODO: tab should insert either \t or indent width spaces
|
||||||
insert_char(cx, '\t');
|
insert_char(cx, '\t');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user