@ -1,7 +1,7 @@
//! 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 crate ::{ movement::Direction , Range, Rope , Selection , Tendril , Transaction } ;
use log ::debug ;
use smallvec ::SmallVec ;
@ -30,7 +30,6 @@ const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{20
// [TODO]
// * delete implementation where it erases the whole bracket (|) -> |
// * do not reduce to cursors; use whole selections, and surround with pair
// * change to multi character pairs to handle cases like placing the cursor in the
// middle of triple quotes, and more exotic pairs like Jinja's {% %}
@ -38,20 +37,18 @@ const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{20
pub fn hook ( doc : & Rope , selection : & Selection , ch : char ) -> Option < Transaction > {
debug ! ( "autopairs hook selection: {:#?}" , selection ) ;
let cursors = selection . clone ( ) . cursors ( doc . slice ( .. ) ) ;
for & ( open , close ) in PAIRS {
if open = = ch {
if open = = close {
return Some ( handle_same ( doc , & cursors , open , CLOSE_BEFORE , OPEN_BEFORE ) ) ;
return Some ( handle_same ( doc , selection , open , CLOSE_BEFORE , OPEN_BEFORE ) ) ;
} else {
return Some ( handle_open ( doc , & cursors , open , close , CLOSE_BEFORE ) ) ;
return Some ( handle_open ( doc , selection , open , close , CLOSE_BEFORE ) ) ;
}
}
if close = = ch {
// && char_at pos == close
return Some ( handle_close ( doc , & cursors , open , close ) ) ;
return Some ( handle_close ( doc , selection , open , close ) ) ;
}
}
@ -66,6 +63,36 @@ fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
doc . get_char ( pos - 1 )
}
/// calculate what the resulting range should be for an auto pair insertion
fn get_next_range (
start_range : & Range ,
offset : usize ,
typed_char : char ,
len_inserted : usize ,
) -> Range {
let end_head = start_range . head + offset + typed_char . len_utf8 ( ) ;
let end_anchor = match ( start_range . len ( ) , start_range . direction ( ) ) {
// if we have a zero width cursor, it shifts to the same number
( 0 , _ ) = > end_head ,
// if we are inserting for a regular one-width cursor, the anchor
// moves with the head
( 1 , Direction ::Forward ) = > end_head - 1 ,
( 1 , Direction ::Backward ) = > end_head + 1 ,
// if we are appending, the anchor stays where it is; only offset
// for multiple range insertions
( _ , Direction ::Forward ) = > start_range . anchor + offset ,
// when we are inserting in front of a selection, we need to move
// the anchor over by however many characters were inserted overall
( _ , Direction ::Backward ) = > start_range . anchor + offset + len_inserted ,
} ;
Range ::new ( end_anchor , end_head )
}
fn handle_open (
doc : & Rope ,
selection : & Selection ,
@ -74,36 +101,32 @@ fn handle_open(
close_before : & str ,
) -> Transaction {
let mut end_ranges = SmallVec ::with_capacity ( selection . len ( ) ) ;
let mut offs = 0 ;
let transaction = Transaction ::change_by_selection ( doc , selection , | start_range | {
let start_head = start_range . head ;
let next = doc . get_char ( start_head ) ;
let end_head = start_head + offs + open . len_utf8 ( ) ;
let end_anchor = if start_range . is_empty ( ) {
end_head
} else {
start_range . anchor + offs
} ;
end_ranges . push ( Range ::new ( end_anchor , end_head ) ) ;
let cursor = start_range . cursor ( doc . slice ( .. ) ) ;
let next_char = doc . get_char ( cursor ) ;
let len_inserted ;
match next {
let change = match next_char {
Some ( ch ) if ! close_before . contains ( ch ) = > {
offs + = open . len_utf8 ( ) ;
( start_head, start_head , Some ( Tendril ::from_char ( open ) ) )
len_inserted = open . len_utf8 ( ) ;
( cursor , cursor , Some ( Tendril ::from_char ( open ) ) )
}
// None | Some(ch) if close_before.contains(ch) => {}
_ = > {
// insert open & close
let pair = Tendril ::from_iter ( [ open , close ] ) ;
offs + = open . len_utf8 ( ) + close . len_utf8 ( ) ;
( start_head , start_head , Some ( pair ) )
}
len_inserted = open . len_utf8 ( ) + close . len_utf8 ( ) ;
( cursor , cursor , Some ( pair ) )
}
} ;
let next_range = get_next_range ( start_range , offs , open , len_inserted ) ;
end_ranges . push ( next_range ) ;
offs + = len_inserted ;
change
} ) ;
let t = transaction . with_selection ( Selection ::new ( end_ranges , selection . primary_index ( ) ) ) ;
@ -117,28 +140,28 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) ->
let mut offs = 0 ;
let transaction = Transaction ::change_by_selection ( doc , selection , | start_range | {
let start_head = start_range . head ;
let next = doc . get_char ( start_head ) ;
let end_head = start_head + offs + close . len_utf8 ( ) ;
let cursor = start_range . cursor ( doc . slice ( .. ) ) ;
let next _char = doc . get_char ( cursor ) ;
let mut len_inserted = 0 ;
let end_anchor = if start_range . is_empty ( ) {
end_head
let change = if next_char = = Some ( close ) {
// return transaction that moves past close
( cursor , cursor , None ) // no-op
} else {
start_range . anchor + offs
len_inserted + = close . len_utf8 ( ) ;
( cursor , cursor , Some ( Tendril ::from_char ( close ) ) )
} ;
end_ranges . push ( Range ::new ( end_anchor , end_head ) ) ;
let next_range = get_next_range ( start_range , offs , close , len_inserted ) ;
end_ranges . push ( next_range ) ;
offs + = len_inserted ;
if next = = Some ( close ) {
// return transaction that moves past close
( start_head , start_head , None ) // no-op
} else {
offs + = close . len_utf8 ( ) ;
( start_head , start_head , Some ( Tendril ::from_char ( close ) ) )
}
change
} ) ;
transaction . with_selection ( Selection ::new ( end_ranges , selection . primary_index ( ) ) )
let t = transaction . with_selection ( Selection ::new ( end_ranges , selection . primary_index ( ) ) ) ;
debug ! ( "auto pair transaction: {:#?}" , t ) ;
t
}
/// handle cases where open and close is the same, or in triples ("""docstring""")
@ -154,42 +177,41 @@ fn handle_same(
let mut offs = 0 ;
let transaction = Transaction ::change_by_selection ( doc , selection , | start_range | {
let start_head = start_range . head ;
let end_head = start_head + offs + token . len_utf8 ( ) ;
let cursor = start_range . cursor ( doc . slice ( .. ) ) ;
let mut len_inserted = 0 ;
// if selection, retain anchor, if cursor, move over
let end_anchor = if start_range . is_empty ( ) {
end_head
} else {
start_range . anchor + offs
} ;
end_ranges . push ( Range ::new ( end_anchor , end_head ) ) ;
let next_char = doc . get_char ( cursor ) ;
let prev_char = prev_char ( doc , cursor ) ;
let next = doc . get_char ( start_head ) ;
let prev = prev_char ( doc , start_head ) ;
if next = = Some ( token ) {
let change = if next_char = = Some ( token ) {
// return transaction that moves past close
( start_head, start_head , None ) // no-op
( cursor , cursor , None ) // no-op
} else {
let mut pair = Tendril ::with_capacity ( 2 * token . len_utf8 ( ) as u32 ) ;
pair . push_char ( token ) ;
// for equal pairs, don't insert both open and close if either
// side has a non-pair char
if ( next . is_none ( ) | | close_before . contains ( next . unwrap ( ) ) )
& & ( prev . is_none ( ) | | open_before . contains ( prev . unwrap ( ) ) )
if ( next _char . is_none ( ) | | close_before . contains ( next _char . unwrap ( ) ) )
& & ( prev _char . is_none ( ) | | open_before . contains ( prev _char . unwrap ( ) ) )
{
pair . push_char ( token ) ;
}
offs + = pair . len ( ) ;
( start_head , start_head , Some ( pair ) )
}
len_inserted + = pair . len ( ) ;
( cursor , cursor , Some ( pair ) )
} ;
let next_range = get_next_range ( start_range , offs , token , len_inserted ) ;
end_ranges . push ( next_range ) ;
offs + = len_inserted ;
change
} ) ;
transaction . with_selection ( Selection ::new ( end_ranges , selection . primary_index ( ) ) )
let t = transaction . with_selection ( Selection ::new ( end_ranges , selection . primary_index ( ) ) ) ;
debug ! ( "auto pair transaction: {:#?}" , t ) ;
t
}
#[ cfg(test) ]
@ -252,7 +274,20 @@ mod test {
& Selection ::single ( 1 , 0 ) ,
PAIRS ,
| open , close | format! ( "{}{}" , open , close ) ,
& Selection ::single ( 1 , 1 ) ,
& Selection ::single ( 2 , 1 ) ,
) ;
}
/// [] -> append ( -> ([])
#[ test ]
fn test_append_blank ( ) {
test_hooks_with_pairs (
// this is what happens when you have a totally blank document and then append
& Rope ::from ( "\n\n" ) ,
& Selection ::single ( 0 , 2 ) ,
PAIRS ,
| open , close | format! ( "\n{}{}\n" , open , close ) ,
& Selection ::single ( 0 , 3 ) ,
) ;
}
@ -276,26 +311,50 @@ mod test {
)
} ,
& Selection ::new (
smallvec ! ( Range ::point( 1 ) , Range ::point ( 4 ) , Range ::point ( 7 ) , ) ,
smallvec ! ( Range ::new( 2 , 1 ) , Range ::new ( 5 , 4 ) , Range ::new ( 8 , 7 ) , ) ,
0 ,
) ,
) ;
}
// [TODO] broken until it works with selections
/// fo[o] -> append ( -> fo[o(])
#[ ignore ]
#[ test ]
fn test_append ( ) {
test_hooks_with_pairs (
& Rope ::from ( "foo ") ,
& Rope ::from ( "foo \n ") ,
& Selection ::single ( 2 , 4 ) ,
PAIRS ,
| open , close | format! ( "foo{}{} ", open , close ) ,
differing_pairs( ) ,
| open , close | format! ( "foo{}{} \n ", open , close ) ,
& Selection ::single ( 2 , 5 ) ,
) ;
}
/// fo[o] fo[o(])
/// fo[o] -> append ( -> fo[o(])
/// fo[o] fo[o(])
#[ test ]
fn test_append_multi ( ) {
test_hooks_with_pairs (
& Rope ::from ( "foo\nfoo\nfoo\n" ) ,
& Selection ::new (
smallvec ! ( Range ::new ( 2 , 4 ) , Range ::new ( 6 , 8 ) , Range ::new ( 10 , 12 ) ) ,
0 ,
) ,
differing_pairs ( ) ,
| open , close | {
format! (
"foo{open}{close}\nfoo{open}{close}\nfoo{open}{close}\n" ,
open = open ,
close = close
)
} ,
& Selection ::new (
smallvec ! ( Range ::new ( 2 , 5 ) , Range ::new ( 8 , 11 ) , Range ::new ( 14 , 17 ) ) ,
0 ,
) ,
) ;
}
/// ([]) -> insert ) -> ()[]
#[ test ]
fn test_insert_close_inside_pair ( ) {
@ -307,7 +366,23 @@ mod test {
& Selection ::single ( 2 , 1 ) ,
* close ,
& doc ,
& Selection ::point ( 2 ) ,
& Selection ::single ( 3 , 2 ) ,
) ;
}
}
/// [(]) -> append ) -> [()]
#[ test ]
fn test_append_close_inside_pair ( ) {
for ( open , close ) in PAIRS {
let doc = Rope ::from ( format! ( "{}{}\n" , open , close ) ) ;
test_hooks (
& doc ,
& Selection ::single ( 0 , 2 ) ,
* close ,
& doc ,
& Selection ::single ( 0 , 3 ) ,
) ;
}
}
@ -323,8 +398,33 @@ mod test {
) ;
let expected_sel = Selection ::new (
// smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),),
smallvec ! ( Range ::point ( 2 ) , Range ::point ( 5 ) , Range ::point ( 8 ) , ) ,
smallvec ! ( Range ::new ( 3 , 2 ) , Range ::new ( 6 , 5 ) , Range ::new ( 9 , 8 ) , ) ,
0 ,
) ;
for ( open , close ) in PAIRS {
let doc = Rope ::from ( format! (
"{open}{close}\n{open}{close}\n{open}{close}\n" ,
open = open ,
close = close
) ) ;
test_hooks ( & doc , & sel , * close , & doc , & expected_sel ) ;
}
}
/// [(]) [()]
/// [(]) -> append ) -> [()]
/// [(]) [()]
#[ test ]
fn test_append_close_inside_pair_multi_cursor ( ) {
let sel = Selection ::new (
smallvec ! ( Range ::new ( 0 , 2 ) , Range ::new ( 3 , 5 ) , Range ::new ( 6 , 8 ) , ) ,
0 ,
) ;
let expected_sel = Selection ::new (
smallvec ! ( Range ::new ( 0 , 3 ) , Range ::new ( 3 , 6 ) , Range ::new ( 6 , 9 ) , ) ,
0 ,
) ;
@ -343,7 +443,7 @@ mod test {
#[ test ]
fn test_insert_open_inside_pair ( ) {
let sel = Selection ::single ( 2 , 1 ) ;
let expected_sel = Selection ::point( 2 ) ;
let expected_sel = Selection ::single( 3 , 2 ) ;
for ( open , close ) in differing_pairs ( ) {
let doc = Rope ::from ( format! ( "{}{}" , open , close ) ) ;
@ -357,11 +457,49 @@ mod test {
}
}
/// [word(]) -> append ( -> [word((]))
#[ test ]
fn test_append_open_inside_pair ( ) {
let sel = Selection ::single ( 0 , 6 ) ;
let expected_sel = Selection ::single ( 0 , 7 ) ;
for ( open , close ) in differing_pairs ( ) {
let doc = Rope ::from ( format! ( "word{}{}" , open , close ) ) ;
let expected_doc = Rope ::from ( format! (
"word{open}{open}{close}{close}" ,
open = open ,
close = close
) ) ;
test_hooks ( & doc , & sel , * open , & expected_doc , & expected_sel ) ;
}
}
/// ([]) -> insert " -> ("[]")
#[ test ]
fn test_insert_nested_open_inside_pair ( ) {
let sel = Selection ::single ( 2 , 1 ) ;
let expected_sel = Selection ::point ( 2 ) ;
let expected_sel = Selection ::single ( 3 , 2 ) ;
for ( outer_open , outer_close ) in differing_pairs ( ) {
let doc = Rope ::from ( format! ( "{}{}" , outer_open , outer_close , ) ) ;
for ( inner_open , inner_close ) in matching_pairs ( ) {
let expected_doc = Rope ::from ( format! (
"{}{}{}{}" ,
outer_open , inner_open , inner_close , outer_close
) ) ;
test_hooks ( & doc , & sel , * inner_open , & expected_doc , & expected_sel ) ;
}
}
}
/// [(]) -> append " -> [("]")
#[ test ]
fn test_append_nested_open_inside_pair ( ) {
let sel = Selection ::single ( 0 , 2 ) ;
let expected_sel = Selection ::single ( 0 , 3 ) ;
for ( outer_open , outer_close ) in differing_pairs ( ) {
let doc = Rope ::from ( format! ( "{}{}" , outer_open , outer_close , ) ) ;
@ -385,21 +523,44 @@ mod test {
& Selection ::single ( 1 , 0 ) ,
PAIRS ,
| open , _ | format! ( "{}word" , open ) ,
& Selection ::point( 1 ) ,
& Selection ::single( 2 , 1 ) ,
)
}
// [TODO] broken until it works with selections
/// [wor]d -> insert ( -> ([wor]d
#[ test ]
#[ ignore ]
fn test_insert_open_with_selection ( ) {
test_hooks_with_pairs (
& Rope ::from ( "word" ) ,
& Selection ::single ( 0, 4 ) ,
& Selection ::single ( 3, 0 ) ,
PAIRS ,
| open , _ | format! ( "{}word" , open ) ,
& Selection ::single ( 1 , 5 ) ,
& Selection ::single ( 4 , 1 ) ,
)
}
/// [wor]d -> append ) -> [wor)]d
#[ test ]
fn test_append_close_inside_non_pair_with_selection ( ) {
let sel = Selection ::single ( 0 , 4 ) ;
let expected_sel = Selection ::single ( 0 , 5 ) ;
for ( _ , close ) in PAIRS {
let doc = Rope ::from ( "word" ) ;
let expected_doc = Rope ::from ( format! ( "wor{}d" , close ) ) ;
test_hooks ( & doc , & sel , * close , & expected_doc , & expected_sel ) ;
}
}
/// foo[ wor]d -> insert ( -> foo([) wor]d
#[ test ]
fn test_insert_open_trailing_word_with_selection ( ) {
test_hooks_with_pairs (
& Rope ::from ( "foo word" ) ,
& Selection ::single ( 7 , 3 ) ,
differing_pairs ( ) ,
| open , close | format! ( "foo{}{} word" , open , close ) ,
& Selection ::single ( 9 , 4 ) ,
)
}
@ -413,7 +574,7 @@ mod test {
fn test_insert_open_after_non_pair ( ) {
let doc = Rope ::from ( "word" ) ;
let sel = Selection ::single ( 5 , 4 ) ;
let expected_sel = Selection ::point( 5 ) ;
let expected_sel = Selection ::single( 6 , 5 ) ;
test_hooks_with_pairs (
& doc ,
@ -431,4 +592,18 @@ mod test {
& expected_sel ,
) ;
}
/// appending with only a cursor should stay a cursor
///
/// [] -> append to end "foo -> "foo[]"
#[ test ]
fn test_append_single_cursor ( ) {
test_hooks_with_pairs (
& Rope ::from ( "\n" ) ,
& Selection ::single ( 0 , 1 ) ,
PAIRS ,
| open , close | format! ( "{}{}\n" , open , close ) ,
& Selection ::single ( 1 , 2 ) ,
) ;
}
}