use hjkl_engine::tag::is_html_filetype;
pub fn autopair_close_for(
ch: char,
filetype: &str,
prev_char: Option<char>,
prev2_char: Option<char>,
) -> Option<char> {
let is_triple_quote_third =
matches!(ch, '"' | '`' | '\'') && prev_char == Some(ch) && prev2_char == Some(ch);
match ch {
'(' => Some(')'),
'[' => Some(']'),
'{' => Some('}'),
'"' => {
if is_triple_quote_third {
None
} else {
Some('"')
}
}
'`' => {
if is_triple_quote_third {
None
} else {
Some('`')
}
}
'<' => {
if is_html_filetype(filetype) {
Some('>')
} else {
None
}
}
'\'' => {
if is_triple_quote_third {
return None;
}
if prev_char.is_some_and(|c| c.is_ascii_alphabetic()) {
None
} else {
Some('\'')
}
}
_ => None,
}
}
pub fn detect_code_fence_opener(line: &str, cursor_col: usize) -> Option<String> {
if cursor_col != line.chars().count() {
return None;
}
let trimmed = line.trim_start();
let backtick_run = trimmed.chars().take_while(|c| *c == '`').count();
if backtick_run < 3 {
return None;
}
let rest = &trimmed[backtick_run..];
if rest.is_empty() {
return None;
}
let all_lang_chars = rest
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '+' || c == '-');
if !all_lang_chars {
return None;
}
Some("`".repeat(backtick_run))
}
#[cfg(test)]
mod abbrev_tests {
use crate::vim::{Abbrev, AbbrevKind, AbbrevTrigger, abbrev_kind, try_abbrev_expand};
use AbbrevKind::{End, Full, NonKw};
const ISK: &str = "@,48-57,_,192-255";
fn make_abbrev(lhs: &str, rhs: &str) -> Abbrev {
Abbrev {
lhs: lhs.to_string(),
rhs: rhs.to_string(),
insert: true,
cmdline: false,
noremap: false,
}
}
fn expand(
abbrevs: &[Abbrev],
before: &str,
mincol: usize,
trig: AbbrevTrigger,
) -> Option<(usize, String)> {
try_abbrev_expand(abbrevs, before, mincol, trig, ISK)
}
#[test]
fn fullid_all_keyword_chars() {
assert_eq!(abbrev_kind("teh", ISK), Full);
assert_eq!(abbrev_kind("abc123", ISK), Full);
assert_eq!(abbrev_kind("_foo", ISK), Full);
}
#[test]
fn endid_ends_with_kw_has_nonkw() {
assert_eq!(abbrev_kind("#i", ISK), End);
assert_eq!(abbrev_kind("#include", ISK), End);
}
#[test]
fn nonid_ends_with_nonkw() {
assert_eq!(abbrev_kind(";;", ISK), NonKw);
assert_eq!(abbrev_kind("->", ISK), NonKw);
}
#[test]
fn fullid_expands_on_space_trigger() {
let abbrevs = [make_abbrev("teh", "the")];
let r = expand(&abbrevs, "teh", 0, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, Some((3, "the".to_string())));
}
#[test]
fn fullid_expands_on_esc_trigger() {
let abbrevs = [make_abbrev("teh", "the")];
let r = expand(&abbrevs, "teh", 0, AbbrevTrigger::Esc);
assert_eq!(r, Some((3, "the".to_string())));
}
#[test]
fn fullid_expands_on_cr_trigger() {
let abbrevs = [make_abbrev("teh", "the")];
let r = expand(&abbrevs, "teh", 0, AbbrevTrigger::Cr);
assert_eq!(r, Some((3, "the".to_string())));
}
#[test]
fn fullid_expands_on_ctrl_bracket() {
let abbrevs = [make_abbrev("teh", "the")];
let r = expand(&abbrevs, "teh", 0, AbbrevTrigger::CtrlBracket);
assert_eq!(r, Some((3, "the".to_string())));
}
#[test]
fn fullid_does_not_expand_on_keyword_trigger() {
let abbrevs = [make_abbrev("teh", "the")];
let r = expand(&abbrevs, "teh", 0, AbbrevTrigger::NonKeyword('a'));
assert_eq!(r, None);
}
#[test]
fn fullid_no_expand_when_lhs_not_at_end() {
let abbrevs = [make_abbrev("teh", "the")];
let r = expand(&abbrevs, "ateh", 0, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, None);
}
#[test]
fn fullid_expands_after_nonkw_prefix() {
let abbrevs = [make_abbrev("teh", "the")];
let r = expand(&abbrevs, "!teh", 0, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, Some((3, "the".to_string())));
}
#[test]
fn fullid_single_char_no_expand_after_nonblank_nonkw() {
let abbrevs = [make_abbrev("a", "b")];
let r = expand(&abbrevs, "!a", 0, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, None);
}
#[test]
fn fullid_single_char_expands_after_space() {
let abbrevs = [make_abbrev("a", "b")];
let r = expand(&abbrevs, " a", 0, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, Some((1, "b".to_string())));
}
#[test]
fn mincol_blocks_consuming_preexisting_text() {
let abbrevs = [make_abbrev("teh", "the")];
let r = expand(&abbrevs, "teh", 3, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, None);
}
#[test]
fn mincol_allows_match_starting_at_mincol() {
let abbrevs = [make_abbrev("teh", "the")];
let r = expand(&abbrevs, "!! teh", 3, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, Some((3, "the".to_string())));
}
#[test]
fn endid_expands_on_space_trigger() {
let abbrevs = [make_abbrev("#i", "#include")];
let r = expand(&abbrevs, "#i", 0, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, Some((2, "#include".to_string())));
}
#[test]
fn endid_expands_on_esc_trigger() {
let abbrevs = [make_abbrev("#i", "#include")];
let r = expand(&abbrevs, "#i", 0, AbbrevTrigger::Esc);
assert_eq!(r, Some((2, "#include".to_string())));
}
#[test]
fn nonid_expands_on_esc_trigger() {
let abbrevs = [make_abbrev(";;", "std::endl;")];
let r = expand(&abbrevs, ";;", 0, AbbrevTrigger::Esc);
assert_eq!(r, Some((2, "std::endl;".to_string())));
}
#[test]
fn nonid_expands_on_cr_trigger() {
let abbrevs = [make_abbrev(";;", "std::endl;")];
let r = expand(&abbrevs, ";;", 0, AbbrevTrigger::Cr);
assert_eq!(r, Some((2, "std::endl;".to_string())));
}
#[test]
fn nonid_does_not_expand_on_nonkw_trigger() {
let abbrevs = [make_abbrev(";;", "std::endl;")];
let r = expand(&abbrevs, ";;", 0, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, None);
}
#[test]
fn nonid_expands_on_ctrl_bracket() {
let abbrevs = [make_abbrev(";;", "std::endl;")];
let r = expand(&abbrevs, ";;", 0, AbbrevTrigger::CtrlBracket);
assert_eq!(r, Some((2, "std::endl;".to_string())));
}
#[test]
fn multiword_rhs_expansion() {
let abbrevs = [make_abbrev("hw", "hello world")];
let r = expand(&abbrevs, "hw", 0, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, Some((2, "hello world".to_string())));
}
#[test]
fn no_match_returns_none() {
let abbrevs = [make_abbrev("teh", "the")];
let r = expand(&abbrevs, "xyz", 0, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, None);
}
#[test]
fn empty_abbrevs_returns_none() {
let r = expand(&[], "teh", 0, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, None);
}
#[test]
fn empty_before_text_returns_none() {
let abbrevs = [make_abbrev("teh", "the")];
let r = expand(&abbrevs, "", 0, AbbrevTrigger::NonKeyword(' '));
assert_eq!(r, None);
}
}