use super::*;
use crate::buffer::{Buffer, BufferId};
use crate::heap_types::LispString;
fn extract_heap_match_string(md: &MatchData, group: usize) -> Option<String> {
let searched = match md.searched_string.as_ref()? {
SearchedString::Heap(val) => SearchedString::Heap(*val),
SearchedString::Owned(text) => SearchedString::Owned(text.clone()),
};
let (start, end) = md.groups.get(group).and_then(|group| *group)?;
searched.with_str(|text| {
let byte_start = char_pos_to_byte(text, start);
let byte_end = char_pos_to_byte(text, end);
text.get(byte_start..byte_end).map(str::to_owned)
})
}
#[test]
fn translate_groups() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("\\(foo\\)"), "(foo)");
}
#[test]
fn translate_alternation() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("foo\\|bar"), "foo|bar");
}
#[test]
fn translate_literal_parens() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("(foo)"), "\\(foo\\)");
}
#[test]
fn translate_literal_braces() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("{3}"), "\\{3\\}");
}
#[test]
fn translate_repetition_braces() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("a\\{3\\}"), "a{3}");
}
#[test]
fn translate_literal_pipe() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("a|b"), "a\\|b");
}
#[test]
fn translate_word_boundary() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("\\<word\\>"), "\\bword\\b");
}
#[test]
fn translate_symbol_boundary() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("\\_<word\\_>"), "\\bword\\b");
}
#[test]
fn translate_buffer_boundaries() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("\\`foo\\'"), "\\Afoo\\z");
}
#[test]
fn translate_character_class_passthrough() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("[a-z]"), "[a-z]");
assert_eq!(translate_emacs_regex("[^0-9]"), "[^0-9]");
}
#[test]
fn translate_character_class_backslash_ranges_like_gnu() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("[+\\-*/=<>]"), "[+/=<>]");
}
#[test]
fn translate_easymenu_command_hint_regexp() {
crate::test_utils::init_test_tracing();
let emacs = r"^[^\]*\(\\\[\([^]]+\)]\)[^\]*$";
assert_eq!(
translate_emacs_regex(emacs),
r"^[^\\]*(\\\[([^\]]+)])[^\\]*$"
);
}
#[test]
fn replace_match_case_capitalizes_each_word_like_gnu() {
crate::test_utils::init_test_tracing();
assert_eq!(apply_match_case("[alice:5]", "Alice"), "[Alice:5]");
assert_eq!(
apply_match_case("h_hello w_world", "Hello World"),
"H_Hello W_World"
);
}
#[test]
fn replace_match_case_upcases_all_caps_matches() {
crate::test_utils::init_test_tracing();
assert_eq!(apply_match_case("foo-bar", "FOO"), "FOO-BAR");
}
#[test]
fn translate_reversed_range_classes() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("[z-a]"), "[^\\s\\S]");
assert_eq!(translate_emacs_regex("[^z-a]"), "[\\s\\S]");
}
#[test]
fn translate_backslash_w() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("\\w+"), "\\w+");
}
#[test]
fn compile_search_pattern_uses_backref_engine_for_supported_captures() {
crate::test_utils::init_test_tracing();
assert!(matches!(
compile_search_pattern("\\([a-z]+\\)-\\([0-9]+\\)", false),
Ok(CompiledSearchPattern::Emacs(_))
));
}
#[test]
fn compile_search_pattern_uses_backref_engine_for_noncapturing_groups() {
crate::test_utils::init_test_tracing();
assert!(matches!(
compile_search_pattern("\\(?:foo\\|bar\\)+", false),
Ok(CompiledSearchPattern::Emacs(_))
));
}
#[test]
fn compile_search_pattern_routes_syntax_classes_through_backref_engine() {
crate::test_utils::init_test_tracing();
assert!(matches!(
compile_search_pattern("\\(defun\\|defvar\\)\\s-+\\(\\w+\\)", false),
Ok(CompiledSearchPattern::Emacs(_))
));
}
#[test]
fn compile_search_pattern_routes_category_classes_through_backref_engine() {
crate::test_utils::init_test_tracing();
assert!(matches!(
compile_search_pattern("[ \t]\\|\\c|.\\|.\\c|", false),
Ok(CompiledSearchPattern::Emacs(_))
));
}
#[test]
fn compile_search_pattern_routes_digit_classes_through_backref_engine() {
crate::test_utils::init_test_tracing();
assert!(matches!(
compile_search_pattern("\\d+", false),
Ok(CompiledSearchPattern::Emacs(_))
));
}
#[test]
fn compile_search_pattern_routes_char_class_escapes_through_backref_engine() {
crate::test_utils::init_test_tracing();
assert!(matches!(
compile_search_pattern("[\\w-]+", false),
Ok(CompiledSearchPattern::Emacs(_))
));
assert!(matches!(
compile_search_pattern("[\\s-]+", false),
Ok(CompiledSearchPattern::Emacs(_))
));
}
#[test]
fn compile_search_pattern_routes_lazy_quantifiers_through_backref_engine() {
crate::test_utils::init_test_tracing();
assert!(matches!(
compile_search_pattern("a.*?b", false),
Ok(CompiledSearchPattern::Emacs(_))
));
assert!(matches!(
compile_search_pattern("a\\{2,4\\}?b", false),
Ok(CompiledSearchPattern::Emacs(_))
));
}
#[test]
fn compile_search_pattern_routes_open_interval_quantifiers_through_backref_engine() {
crate::test_utils::init_test_tracing();
assert!(matches!(
compile_search_pattern("a\\{,2\\}b", false),
Ok(CompiledSearchPattern::Emacs(_))
));
}
#[test]
fn compile_search_pattern_routes_explicit_numbered_groups_through_backref_engine() {
crate::test_utils::init_test_tracing();
assert!(matches!(
compile_search_pattern("\\(?1:[^}]*\\)", false),
Ok(CompiledSearchPattern::Emacs(_))
));
assert!(matches!(
compile_search_pattern("\\(?9:.*?\\)", false),
Ok(CompiledSearchPattern::Emacs(_))
));
}
#[test]
fn compile_search_pattern_routes_symbol_boundaries_through_backref_engine() {
crate::test_utils::init_test_tracing();
assert!(matches!(
compile_search_pattern("\\_<foo\\_>", false),
Ok(CompiledSearchPattern::Emacs(_))
));
}
#[test]
fn compile_search_pattern_routes_bracket_section_anchor_through_backref_engine() {
crate::test_utils::init_test_tracing();
assert!(matches!(
compile_search_pattern("\\`\\[\\([^]]+\\)\\]\\'", true),
Ok(CompiledSearchPattern::Emacs(_))
));
}
#[test]
fn string_match_supported_capture_pattern_uses_backref_engine_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result =
string_match_full_with_case_fold("\\([a-z]+\\)-\\([0-9]+\\)", "foo-123", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 7)));
assert_eq!(md.groups[1], Some((0, 3)));
assert_eq!(md.groups[2], Some((4, 7)));
}
#[test]
fn string_match_noncapturing_group_pattern_uses_backref_engine_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result =
string_match_full_with_case_fold("\\(?:foo\\|bar\\)+", "foobar", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 6)));
assert_eq!(md.groups.len(), 1);
}
#[test]
fn string_match_syntax_class_pattern_uses_backref_engine_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold(
"\\(defun\\|defvar\\)\\s-+\\(\\w+\\)",
"defvar foo",
0,
false,
&mut md,
);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 10)));
assert_eq!(md.groups[1], Some((0, 6)));
assert_eq!(md.groups[2], Some((7, 10)));
}
#[test]
fn string_match_word_syntax_class_pattern_uses_backref_engine_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("\\sw+", "foo_bar", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 7)));
}
#[test]
fn string_match_category_escape_pattern_uses_backref_engine_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("\\c|.", "éx", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 2)));
}
#[test]
fn string_match_match_at_point_escape_uses_backref_engine_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("\\=foo", "foo", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 3)));
}
#[test]
fn string_match_match_at_point_escape_respects_nonzero_start() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("\\=foo", "xxfoo", 2, false, &mut md);
assert_eq!(result, Ok(Some(2)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((2, 5)));
}
#[test]
fn string_match_match_at_point_escape_does_not_skip_past_start() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("\\=foo", "xxafoo", 2, false, &mut md);
assert_eq!(result, Ok(None));
assert!(md.is_none());
}
#[test]
fn string_match_digit_escape_uses_backref_engine_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("\\d+", "123x", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 3)));
}
#[test]
fn string_match_control_escape_uses_backref_engine_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("a\\tb", "a\tb", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 3)));
}
#[test]
fn string_match_char_class_word_escape_uses_backref_engine_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("[\\w-]+", "foo-bar!", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 7)));
}
#[test]
fn string_match_char_class_syntax_escape_uses_backref_engine_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("[\\s-]+", " \tfoo", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 2)));
}
#[test]
fn string_match_lazy_quantifier_preserves_fallback_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("a.*?b", "aXXbYYb", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 4)));
}
#[test]
fn string_match_lazy_plus_quantifier_prefers_shorter_match() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("a.+?b", "aXXbYYb", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 4)));
}
#[test]
fn string_match_lazy_optional_quantifier_prefers_zero_width_choice() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("ab??c", "abc", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 3)));
}
#[test]
fn string_match_lazy_counted_quantifier_prefers_shorter_match() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("a\\{2,4\\}?b", "aaaab", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 5)));
}
#[test]
fn string_match_open_interval_quantifier_matches_gnu_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("a\\{,2\\}b", "aab", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 3)));
}
#[test]
fn string_match_explicit_numbered_group_preserves_group_slot() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("\\(?9:[A-Z]+\\)", "xxABCyy", 0, false, &mut md);
assert_eq!(result, Ok(Some(2)));
let md = md.expect("match data");
assert_eq!(md.groups.len(), 10);
assert_eq!(md.groups[0], Some((2, 5)));
assert!(md.groups[1..9].iter().all(Option::is_none));
assert_eq!(md.groups[9], Some((2, 5)));
}
#[test]
fn string_match_symbol_boundary_pattern_uses_backref_engine_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("\\_<foo\\_>", "x foo y", 0, false, &mut md);
assert_eq!(result, Ok(Some(2)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((2, 5)));
}
#[test]
fn string_match_posix_upper_class_folds_to_alpha_under_case_fold() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result =
string_match_full_with_case_fold("[[:upper:]]+", "helloWORLDfoo", 0, true, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 13)));
}
#[test]
fn string_match_posix_upper_class_folds_to_alpha_on_lisp_string() {
crate::test_utils::init_test_tracing();
let mut md = None;
let string = LispString::new("helloWORLDfoo".to_string(), false);
let result = string_match_full_with_case_fold_source_lisp(
"[[:upper:]]+",
&string,
SearchedString::Owned("helloWORLDfoo".to_string()),
0,
true,
&mut md,
);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 13)));
}
#[test]
fn string_match_anchored_operator_char_class_mirrors_gnu_bracket_closing() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result =
string_match_full_with_case_fold("\\`[-+*/=<>!&|(){}\\[\\];,.]", "=", 0, true, &mut md);
assert_eq!(result, Ok(None));
assert!(md.is_none());
}
#[test]
fn string_match_anchored_operator_char_class_on_lisp_slice_mirrors_gnu_bracket_closing() {
crate::test_utils::init_test_tracing();
let mut md = None;
let source = LispString::new("x = 42;".to_string(), false);
let slice = source.slice(2, source.byte_len()).expect("slice");
let result = string_match_full_with_case_fold_source_lisp(
"\\`[-+*/=<>!&|(){}\\[\\];,.]",
&slice,
SearchedString::Owned(slice.as_str().to_string()),
0,
true,
&mut md,
);
assert_eq!(result, Ok(None));
assert!(md.is_none());
}
#[test]
fn heap_match_string_on_lisp_slice_mirrors_gnu_bracket_closing() {
crate::test_utils::init_test_tracing();
let mut md = None;
let source = LispString::new("x = 42;".to_string(), false);
let slice = source.slice(2, source.byte_len()).expect("slice");
let slice_val = crate::emacs_core::value::Value::string(slice.as_str());
let stored_slice = slice_val.as_lisp_string().unwrap().clone();
let result = string_match_full_with_case_fold_source_lisp(
"\\`[-+*/=<>!&|(){}\\[\\];,.]",
&stored_slice,
SearchedString::Heap(slice_val),
0,
true,
&mut md,
);
assert_eq!(result, Ok(None));
assert!(md.is_none());
}
#[test]
fn heap_tokenizer_loop_mirrors_gnu_single_char_operator_behavior() {
crate::test_utils::init_test_tracing();
let code = LispString::new(
"let x = 42; if x >= 10 && x != 0 { return x + 1; }".to_string(),
false,
);
let keywords = ["if", "else", "while", "return", "let", "fn"];
let patterns = [
("\\`[ \t\n]+", "skip"),
("\\`[0-9]+\\(?:\\.[0-9]+\\)?", "number"),
("\\`\"[^\"]*\"", "string"),
("\\`\\(?:==\\|!=\\|<=\\|>=\\|&&\\|||\\|->\\)", "operator"),
("\\`[-+*/=<>!&|(){}\\[\\];,.]", "operator"),
("\\`[a-zA-Z_][a-zA-Z0-9_]*", "identifier"),
];
let mut pos = 0usize;
let mut tokens = Vec::new();
while pos < code.byte_len() {
let rest = code.slice(pos, code.byte_len()).expect("rest slice");
let rest_val = crate::emacs_core::value::Value::string(rest.as_str());
let stored_rest = rest_val.as_lisp_string().unwrap().clone();
let mut matched = false;
for (pattern, mut kind) in patterns {
if matched {
break;
}
let mut md = None;
if let Ok(Some(_)) = string_match_full_with_case_fold_source_lisp(
pattern,
&stored_rest,
SearchedString::Heap(rest_val),
0,
true,
&mut md,
) {
let md = md.expect("match data");
let text = extract_heap_match_string(&md, 0).expect("matched text");
pos += text.len();
if kind != "skip" {
if kind == "identifier" && keywords.contains(&text.as_str()) {
kind = "keyword";
}
tokens.push((kind.to_string(), text));
}
matched = true;
}
}
if !matched {
pos += 1;
}
}
assert_eq!(
tokens,
vec![
("keyword".to_string(), "let".to_string()),
("identifier".to_string(), "x".to_string()),
("number".to_string(), "42".to_string()),
("keyword".to_string(), "if".to_string()),
("identifier".to_string(), "x".to_string()),
("operator".to_string(), ">=".to_string()),
("number".to_string(), "10".to_string()),
("operator".to_string(), "&&".to_string()),
("identifier".to_string(), "x".to_string()),
("operator".to_string(), "!=".to_string()),
("number".to_string(), "0".to_string()),
("keyword".to_string(), "return".to_string()),
("identifier".to_string(), "x".to_string()),
("number".to_string(), "1".to_string()),
]
);
}
#[test]
fn string_match_bracket_section_anchor_pattern_matches_whole_string() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result =
string_match_full_with_case_fold("\\`\\[\\([^]]+\\)\\]\\'", "[database]", 0, true, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 10)));
assert_eq!(md.groups[1], Some((1, 9)));
}
#[test]
fn string_match_line_anchor_pattern_uses_backref_engine_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("^foo$", "foo", 0, false, &mut md);
assert_eq!(result, Ok(Some(0)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 3)));
}
#[test]
fn string_match_line_anchor_pattern_respects_multiline_semantics() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full_with_case_fold("^foo$", "a\nfoo\nb", 0, false, &mut md);
assert_eq!(result, Ok(Some(2)));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((2, 5)));
}
#[test]
fn translate_complex_pattern() {
crate::test_utils::init_test_tracing();
let emacs = "\\(defun\\|defvar\\)\\s-+\\(\\w+\\)";
let rust = translate_emacs_regex(emacs);
assert_eq!(rust, "(defun|defvar)\\s+(\\w+)");
}
#[test]
fn translate_explicit_numbered_group_keeps_fallback_compilable() {
crate::test_utils::init_test_tracing();
let emacs = "\\(?9:.*?\\)";
assert_eq!(translate_emacs_regex(emacs), "(.*?)");
}
#[test]
fn translate_open_interval_quantifier_keeps_fallback_compilable() {
crate::test_utils::init_test_tracing();
let emacs = "a\\{,2\\}b";
assert_eq!(translate_emacs_regex(emacs), "a{0,2}b");
}
#[test]
fn translate_category_escape_keeps_fill_patterns_compilable() {
crate::test_utils::init_test_tracing();
let emacs = "[ \t]\\|\\c|.\\|.\\c|";
let rust = translate_emacs_regex(emacs);
assert_eq!(rust, "[ \t]|[^\\x00-\\x7F].|.[^\\x00-\\x7F]");
}
#[test]
fn translate_empty_pattern() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex(""), "");
}
#[test]
fn translate_no_special_chars() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("hello"), "hello");
}
#[test]
fn translate_escaped_backslash() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("\\\\"), "\\\\");
}
#[test]
fn translate_multibyte_literals() {
crate::test_utils::init_test_tracing();
assert_eq!(translate_emacs_regex("\\(é\\)"), "(é)");
assert_eq!(translate_emacs_regex("[éx]"), "[éx]");
assert_eq!(translate_emacs_regex("\\é"), "é");
assert_eq!(translate_emacs_regex("\\😀"), "😀");
}
#[test]
fn trivial_regexp_matches_gnu_meta_rules() {
crate::test_utils::init_test_tracing();
assert!(trivial_regexp_p("hello\\.txt"));
assert!(trivial_regexp_p("\\😀"));
assert!(!trivial_regexp_p("he.*o"));
assert!(!trivial_regexp_p("\\(group\\)"));
assert!(!trivial_regexp_p("\\1"));
assert!(!trivial_regexp_p("trailing\\"));
}
#[test]
fn string_match_basic() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full("he..o", "hello world", 0, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(0));
let md = md.unwrap();
assert_eq!(md.groups[0], Some((0, 5)));
assert_eq!(md.searched_string_text(), Some("hello world".to_string()));
}
#[test]
fn string_match_with_groups() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full("\\(\\w+\\)@\\(\\w+\\)", "user@host", 0, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(0));
let md = md.unwrap();
assert_eq!(md.groups.len(), 3); assert_eq!(md.groups[0], Some((0, 9)));
assert_eq!(md.groups[1], Some((0, 4))); assert_eq!(md.groups[2], Some((5, 9))); }
#[test]
fn string_match_with_multibyte_group_literal() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full("\\(é\\)", "aéx", 0, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(1));
let md = md.unwrap();
assert_eq!(md.groups[0], Some((1, 2))); assert_eq!(md.groups[1], Some((1, 2))); }
#[test]
fn string_match_with_escaped_multibyte_literal() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full("\\é", "aéx", 0, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(1));
}
#[test]
fn string_match_trivial_escaped_literal_uses_character_positions() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full("\\.", "a.b", 0, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(1));
let md = md.unwrap();
assert_eq!(md.groups[0], Some((1, 2)));
}
#[test]
fn string_match_backreference_reuses_captured_text() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full("\\(..\\)\\1", "zzabab", 0, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(2));
let md = md.unwrap();
assert_eq!(md.groups[0], Some((2, 6)));
assert_eq!(md.groups[1], Some((2, 4)));
}
#[test]
fn looking_at_string_backreference_matches_at_start() {
crate::test_utils::init_test_tracing();
let mut md = None;
let matched = looking_at_string("\\(x\\)\\1\\1", "xxx!", false, &mut md).unwrap();
assert!(matched);
let md = md.unwrap();
assert_eq!(md.groups[0], Some((0, 3)));
assert_eq!(md.groups[1], Some((0, 1)));
}
#[test]
fn re_search_forward_backreference_word_boundary() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("the the cat");
let mut md = None;
let result = re_search_forward(
&mut buf,
"\\b\\(\\w+\\) \\1\\b",
None,
false,
false,
&mut md,
);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(7));
let md = md.unwrap();
assert_eq!(md.groups[0], Some((0, 7)));
assert_eq!(md.groups[1], Some((0, 3)));
}
#[test]
fn string_match_backreference_with_char_class_group() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full("\\([a-z]+\\) \\1", "the the cat", 0, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(0));
let md = md.unwrap();
assert_eq!(md.groups[0], Some((0, 7)));
assert_eq!(md.groups[1], Some((0, 3)));
}
#[test]
fn string_match_template_interpolation_pattern() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full(r"{{\([^}]+\)}}", "x {{name}} y", 0, &mut md).unwrap();
assert_eq!(result, Some(2));
let md = md.unwrap();
assert_eq!(md.groups[0], Some((2, 10)));
assert_eq!(md.groups[1], Some((4, 8)));
}
#[test]
fn string_match_template_foreach_pattern() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full(
r"{%foreach \([^ ]+\) in \([^%]+\)%}\(\(?:.\|\n\)*?\){%endforeach%}",
"Items: {%foreach x in items%}[{{x}}] {%endforeach%}",
0,
&mut md,
)
.unwrap();
assert_eq!(result, Some(7));
let md = md.unwrap();
assert_eq!(md.groups[1], Some((17, 18)));
assert_eq!(md.groups[2], Some((22, 27)));
assert_eq!(md.groups[3], Some((29, 37)));
}
#[test]
fn string_match_template_conditional_pattern() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full(
r"{%if \([^%]+\)%}\(\(?:.\|\n\)*?\){%else%}\(\(?:.\|\n\)*?\){%endif%}",
"{%if admin%}[ADMIN]{%else%}[USER]{%endif%}",
0,
&mut md,
)
.unwrap();
assert_eq!(result, Some(0));
let md = md.unwrap();
assert_eq!(md.groups[1], Some((5, 10)));
assert_eq!(md.groups[2], Some((12, 19)));
assert_eq!(md.groups[3], Some((27, 33)));
}
#[test]
fn string_match_with_start_offset() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full("world", "hello world", 6, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(6));
}
#[test]
fn string_match_no_match() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full("xyz", "hello world", 0, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), None);
assert!(md.is_none());
}
#[test]
fn string_match_emacs_alternation() {
crate::test_utils::init_test_tracing();
let mut md = None;
let result = string_match_full("\\(foo\\|bar\\)", "test bar baz", 0, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(5));
let md = md.unwrap();
assert_eq!(md.groups[1], Some((5, 8))); }
fn make_test_buffer(text: &str) -> Buffer {
let mut buf = Buffer::new(BufferId(1), "test".to_string());
buf.insert(text);
buf.goto_byte(0);
buf
}
#[test]
fn search_forward_basic() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("hello world");
let mut md = None;
let result = search_forward(&mut buf, "world", None, false, false, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(11)); assert_eq!(buf.pt, 11);
let md = md.unwrap();
assert_eq!(md.groups[0], Some((6, 11)));
}
#[test]
fn search_forward_not_found_noerror() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("hello world");
let mut md = None;
let result = search_forward(&mut buf, "xyz", None, true, false, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), None);
assert_eq!(buf.pt, 0); }
#[test]
fn search_forward_not_found_error() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("hello world");
let mut md = None;
let result = search_forward(&mut buf, "xyz", None, false, false, &mut md);
assert!(result.is_err());
}
#[test]
fn search_forward_case_fold_true() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("A");
let mut md = None;
let result = search_forward(&mut buf, "a", None, false, true, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(1));
}
#[test]
fn search_forward_case_fold_true_unicode_literal() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("Äx");
let mut md = None;
let result = search_forward(&mut buf, "ä", None, false, true, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some('Ä'.len_utf8()));
}
#[test]
fn re_search_forward_trivial_regexp_follows_literal_case_fold_path() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("A.b");
let mut md = None;
let result = re_search_forward(&mut buf, "a\\.", None, false, true, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(2));
let md = md.unwrap();
assert_eq!(md.groups[0], Some((0, 2)));
}
#[test]
fn search_forward_with_bound() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("hello world");
let mut md = None;
let result = search_forward(&mut buf, "world", Some(5), true, false, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), None);
}
#[test]
fn search_forward_from_middle() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("aaa bbb aaa");
buf.goto_byte(4); let mut md = None;
let result = search_forward(&mut buf, "aaa", None, false, false, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(11)); }
#[test]
fn search_backward_basic() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("hello world");
buf.goto_byte(11); let mut md = None;
let result = search_backward(&mut buf, "hello", None, false, false, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(0)); assert_eq!(buf.pt, 0);
}
#[test]
fn search_backward_not_found() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("hello world");
buf.goto_byte(11);
let mut md = None;
let result = search_backward(&mut buf, "xyz", None, true, false, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), None);
}
#[test]
fn search_backward_finds_last_occurrence() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("aaa bbb aaa");
buf.goto_byte(11); let mut md = None;
let result = search_backward(&mut buf, "aaa", None, false, false, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(8));
assert_eq!(buf.pt, 8);
}
#[test]
fn search_backward_case_fold_true_unicode_literal() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("Ää");
buf.goto_byte("Ää".len());
let mut md = None;
let result = search_backward(&mut buf, "ä", None, false, true, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some('Ä'.len_utf8()));
assert_eq!(buf.pt, 'Ä'.len_utf8());
}
#[test]
fn re_search_forward_basic() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("foo 123 bar");
let mut md = None;
let result = re_search_forward(&mut buf, "[0-9]+", None, false, false, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(7)); assert_eq!(buf.pt, 7);
let md = md.unwrap();
assert_eq!(md.groups[0], Some((4, 7)));
}
#[test]
fn re_search_forward_with_groups() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("name: John");
let mut md = None;
let result = re_search_forward(
&mut buf,
"\\(\\w+\\): \\(\\w+\\)",
None,
false,
false,
&mut md,
);
assert!(result.is_ok());
let md = md.unwrap();
assert_eq!(md.groups.len(), 3);
assert_eq!(md.groups[1], Some((0, 4))); assert_eq!(md.groups[2], Some((6, 10))); }
#[test]
fn re_search_forward_multiline_anchor_respects_real_line_start() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("alpha=1\nbeta=2\ngamma=3\n");
let mut md = None;
let first = re_search_forward(
&mut buf,
"^\\([^=]+\\)=\\([0-9]+\\)$",
None,
false,
false,
&mut md,
)
.expect("first search should succeed");
assert_eq!(first, Some("alpha=1".len()));
let first_md = md.as_ref().expect("match data for first search");
let (s1, e1) = first_md.groups[1].unwrap();
assert_eq!(buf.text.text_range(s1, e1), "alpha");
let second = re_search_forward(
&mut buf,
"^\\([^=]+\\)=\\([0-9]+\\)$",
None,
false,
false,
&mut md,
)
.expect("second search should succeed");
assert_eq!(second, Some("alpha=1\nbeta=2".len()));
let second_md = md.as_ref().expect("match data for second search");
let (s1, e1) = second_md.groups[1].unwrap();
assert_eq!(buf.text.text_range(s1, e1), "beta");
let (s2, e2) = second_md.groups[2].unwrap();
assert_eq!(buf.text.text_range(s2, e2), "2");
}
#[test]
fn re_search_backward_basic() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("abc 123 def 456");
buf.goto_byte(15); let mut md = None;
let result = re_search_backward(&mut buf, "[0-9]+", None, false, false, &mut md);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(14));
assert_eq!(buf.pt, 14);
}
#[test]
fn re_search_backward_finds_nullable_match_at_point() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("abc\n");
buf.goto_byte(3); let mut md = None;
let result = re_search_backward(&mut buf, "\\(?:$\\)\\=", Some(0), true, false, &mut md);
assert_eq!(result, Ok(Some(3)));
assert_eq!(buf.pt, 3);
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((3, 3)));
}
#[test]
fn re_search_forward_finds_nullable_match_at_buffer_end() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("abc");
buf.goto_byte(3);
let mut md = None;
let result = re_search_forward(&mut buf, "\\=", None, true, false, &mut md);
assert_eq!(result, Ok(Some(3)));
assert_eq!(buf.pt, 3);
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((3, 3)));
}
#[test]
fn looking_at_matches() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("hello world");
buf.goto_byte(0);
let mut md = None;
let result = looking_at(&buf, "hello", true, &mut md);
assert!(result.is_ok());
assert!(result.unwrap());
assert!(md.is_some());
}
#[test]
fn looking_at_no_match() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("hello world");
buf.goto_byte(0);
let mut md = None;
let result = looking_at(&buf, "world", true, &mut md);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn looking_at_from_middle() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("hello world");
buf.goto_byte(6); let mut md = None;
let result = looking_at(&buf, "world", true, &mut md);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn looking_at_defaults_to_case_fold() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("A");
buf.goto_byte(0);
let mut md = None;
let result = looking_at(&buf, "a", true, &mut md);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn looking_at_respects_case_fold_false() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("A");
buf.goto_byte(0);
let mut md = None;
let result = looking_at(&buf, "a", false, &mut md);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn looking_at_with_groups() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("foo123bar");
buf.goto_byte(0);
let mut md = None;
let result = looking_at(&buf, "\\(\\w+\\)\\([0-9]+\\)", true, &mut md);
assert!(result.is_ok());
assert!(result.unwrap());
let md = md.unwrap();
assert!(md.groups[0].is_some());
}
#[test]
fn looking_at_character_class_backslash_range_like_gnu() {
crate::test_utils::init_test_tracing();
let mut md = None;
let buf = make_test_buffer("/");
let result = looking_at(&buf, "[+\\-*/=<>]", false, &mut md);
assert_eq!(result, Ok(true));
let md = md.expect("match data");
assert_eq!(md.groups[0], Some((0, 1)));
let mut md = None;
let buf = make_test_buffer("*");
assert_eq!(looking_at(&buf, "[+\\-*/=<>]", false, &mut md), Ok(false));
let mut md = None;
let buf = make_test_buffer("-");
assert_eq!(looking_at(&buf, "[+\\-*/=<>]", false, &mut md), Ok(false));
}
#[test]
fn replace_match_literal() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("hello world");
let mut md = None;
let _ = re_search_forward(&mut buf, "world", None, false, false, &mut md);
let result = replace_match_buffer(&mut buf, "rust", false, true, 0, &md);
assert!(result.is_ok());
let content = buf.text.text_range(0, buf.text.len());
assert_eq!(content, "hello rust");
}
#[test]
fn replace_match_with_backref() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("hello world");
buf.goto_byte(0);
let mut md = None;
let _ = re_search_forward(&mut buf, "\\(hello\\)", None, false, false, &mut md);
let result = replace_match_buffer(&mut buf, "\\1 there", false, false, 0, &md);
assert!(result.is_ok());
let content = buf.text.text_range(0, buf.text.len());
assert_eq!(content, "hello there world");
}
#[test]
fn replace_match_applies_case_pattern() {
crate::test_utils::init_test_tracing();
let mut md = None;
let _ = string_match_full("FOO", "FOO", 0, &mut md);
let replaced = replace_match_string("FOO", "bar", false, false, 0, &md).unwrap();
assert_eq!(replaced, "BAR");
let _ = string_match_full("Foo", "Foo", 0, &mut md);
let replaced = replace_match_string("Foo", "bar", false, false, 0, &md).unwrap();
assert_eq!(replaced, "Bar");
}
#[test]
fn replace_match_subexp_replaces_requested_group() {
crate::test_utils::init_test_tracing();
let mut md = None;
let _ = string_match_full("\\([a-z]+\\)\\([0-9]+\\)", "abc123", 0, &mut md);
let replaced = replace_match_string("abc123", "X", false, false, 2, &md).unwrap();
assert_eq!(replaced, "abcX");
}
#[test]
fn replace_match_subexp_errors_when_missing() {
crate::test_utils::init_test_tracing();
let mut md = None;
let _ = string_match_full("\\([a-z]+\\)?\\([0-9]+\\)", "123", 0, &mut md);
let err = replace_match_string("123", "X", false, false, 1, &md).unwrap_err();
assert_eq!(err, REPLACE_MATCH_SUBEXP_MISSING);
}
#[test]
fn replace_match_preserves_multibyte_replacement_literals() {
crate::test_utils::init_test_tracing();
let mut md = None;
let _ = string_match_full("x", "x", 0, &mut md);
let replaced = replace_match_string("x", "éz", false, false, 0, &md).unwrap();
assert_eq!(replaced, "éz");
}
#[test]
fn replace_match_preserves_multibyte_replacement_with_backref() {
crate::test_utils::init_test_tracing();
let mut md = None;
let _ = string_match_full("\\(x\\)", "x", 0, &mut md);
let replaced = replace_match_string("x", "\\1é", false, false, 0, &md).unwrap();
assert_eq!(replaced, "xé");
}
#[test]
fn search_forward_then_match_string() {
crate::test_utils::init_test_tracing();
let mut buf = make_test_buffer("The quick brown fox");
let mut md = None;
let _ = re_search_forward(
&mut buf,
"\\(quick\\) \\(brown\\)",
None,
false,
false,
&mut md,
);
let md = md.as_ref().unwrap();
let (s0, e0) = md.groups[0].unwrap();
assert_eq!(buf.text.text_range(s0, e0), "quick brown");
let (s1, e1) = md.groups[1].unwrap();
assert_eq!(buf.text.text_range(s1, e1), "quick");
let (s2, e2) = md.groups[2].unwrap();
assert_eq!(buf.text.text_range(s2, e2), "brown");
}
#[test]
fn string_match_then_match_data() {
crate::test_utils::init_test_tracing();
let mut md = None;
let _ = string_match_full("\\([0-9]+\\)-\\([0-9]+\\)", "date: 2024-01-15", 0, &mut md);
let md = md.as_ref().unwrap();
let string = md.searched_string_text().unwrap();
let (s0, _e0) = md.groups[0].unwrap();
assert_eq!(s0, 6);
let (s1, e1) = md.groups[1].unwrap();
assert_eq!(&string[s1..e1], "2024");
let (s2, e2) = md.groups[2].unwrap();
assert_eq!(&string[s2..e2], "01");
}
#[test]
fn string_match_optional_group() {
crate::test_utils::init_test_tracing();
let mut md = None;
let _ = string_match_full("\\(foo\\)\\(bar\\)?", "fooXYZ", 0, &mut md);
let md = md.as_ref().unwrap();
assert_eq!(md.groups[1], Some((0, 3))); assert_eq!(md.groups[2], None); }
#[test]
fn string_match_start_offset_respects_real_line_start() {
crate::test_utils::init_test_tracing();
let mut md = None;
let source = "alpha=1\nbeta=2\ngamma=3";
let start = "alpha=1".len();
let result = string_match_full("^\\([^=]+\\)=\\([0-9]+\\)$", source, start, &mut md)
.expect("string match should succeed");
assert_eq!(result, Some("alpha=1\n".chars().count()));
let md = md.as_ref().expect("match data");
let searched = md.searched_string_text().expect("searched string");
let (s1, e1) = md.groups[1].unwrap();
let byte_s1 = searched
.char_indices()
.nth(s1)
.map(|(i, _)| i)
.unwrap_or(searched.len());
let byte_e1 = searched
.char_indices()
.nth(e1)
.map(|(i, _)| i)
.unwrap_or(searched.len());
assert_eq!(&searched[byte_s1..byte_e1], "beta");
}
#[test]
fn test_lazy_interval() {
crate::test_utils::init_test_tracing();
use crate::emacs_core::regex_emacs::{DefaultSyntaxLookup, search_pattern};
let syn = DefaultSyntaxLookup;
let r = search_pattern("a\\{1,3\\}b", "aaab", 0, false, &syn, 0);
let (_, regs) = r.unwrap().expect("should match");
assert_eq!(regs.start[0], 0);
assert_eq!(regs.end[0], 4); }