Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
watch_file shell.nix
watch_file default.nix
watch_file flake.lock
watch_file rust-toolchain.toml

# try to use flakes, if it fails use normal nix (ie. shell.nix)
use flake || use nix
eval "$shellHook"
> 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
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
}