Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
use crate::{Range, RopeSlice, Selection, Syntax};
use tree_sitter::Node;

pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
    select_node_impl(syntax, text, selection, |descendant, from, to| {
        if descendant.start_byte() == from && descendant.end_byte() == to {
            descendant.parent()
        } else {
            Some(descendant)
        }
    })
}

pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
    select_node_impl(syntax, text, selection, |descendant, _from, _to| {
        descendant.child(0).or(Some(descendant))
    })
}

pub fn select_sibling<F>(
    syntax: &Syntax,
    text: RopeSlice,
    selection: Selection,
    sibling_fn: &F,
) -> Selection
where
    F: Fn(Node) -> Option<Node>,
{
    select_node_impl(syntax, text, selection, |descendant, _from, _to| {
        find_sibling_recursive(descendant, sibling_fn)
    })
}

fn find_sibling_recursive<F>(node: Node, sibling_fn: F) -> Option<Node>
where
    F: Fn(Node) -> Option<Node>,
{
    sibling_fn(node).or_else(|| {
        node.parent()
            .and_then(|node| find_sibling_recursive(node, sibling_fn))
    })
}

fn select_node_impl<F>(
    syntax: &Syntax,
    text: RopeSlice,
    selection: Selection,
    select_fn: F,
) -> Selection
where
    F: Fn(Node, usize, usize) -> Option<Node>,
{
    let tree = syntax.tree();

    selection.transform(|range| {
        let from = text.char_to_byte(range.from());
        let to = text.char_to_byte(range.to());

        let node = match tree
            .root_node()
            .descendant_for_byte_range(from, to)
            .and_then(|node| select_fn(node, from, to))
        {
            Some(node) => node,
            None => return range,
        };

        let from = text.byte_to_char(node.start_byte());
        let to = text.byte_to_char(node.end_byte());

        if range.head < range.anchor {
            Range::new(to, from)
        } else {
            Range::new(from, to)
        }
    })
}
t margin = 1; // TODO: margin is hardcoded 1 but could easily be 0 changes.push((pos, pos + token.len() + margin, None)) } } } Transaction::change(doc, changes.into_iter()) } #[cfg(test)] mod test { use super::*; #[test] fn test_find_line_comment() { use crate::State; // four lines, two space indented, except for line 1 which is blank. let doc = Rope::from(" 1\n\n 2\n 3"); let mut state = State::new(doc); // select whole document state.selection = Selection::single(0, state.doc.len_chars() - 1); let text = state.doc.slice(..); let res = find_line_comment("//", text, 0..3); // (commented = true, skipped = [line 1], min = col 2) assert_eq!(res, (false, vec![1], 2)); // comment let transaction = toggle_line_comments(&state.doc, &state.selection); transaction.apply(&mut state.doc); state.selection = state.selection.clone().map(transaction.changes()); assert_eq!(state.doc, " // 1\n\n // 2\n // 3"); // uncomment let transaction = toggle_line_comments(&state.doc, &state.selection); transaction.apply(&mut state.doc); state.selection = state.selection.clone().map(transaction.changes()); assert_eq!(state.doc, " 1\n\n 2\n 3"); // TODO: account for no margin after comment // TODO: account for uncommenting with uneven comment indentation } }