pub fn find_type_range(s: &str) -> Option<(usize, usize)> {
let trimmed = s.trim_start();
if !trimmed.starts_with('{') {
return None;
}
let offset = s.len() - trimmed.len();
let mut brace_count = 0;
for (idx, ch) in trimmed.char_indices() {
match ch {
'{' => {
brace_count += 1;
}
'}' => {
brace_count -= 1;
if brace_count == 0 {
return Some((offset, offset + idx + 1));
}
}
_ => {}
}
}
None
}
pub fn find_type_name_range(s: &str) -> Option<(usize, usize)> {
if !s.trim_start().starts_with('[') {
return find_token_range(s);
}
let mut bracket = 0;
let mut start = None;
for (idx, ch) in s.char_indices() {
if ch.is_whitespace() {
if bracket != 0 {
continue;
}
if let Some(start) = start {
return Some((start, idx));
}
} else {
if ch == '[' {
bracket += 1;
}
if ch == ']' {
bracket -= 1;
}
if start.is_none() {
start = Some(idx);
}
}
}
if let Some(start) = start
&& bracket == 0
{
return Some((start, s.len()));
}
None
}
pub fn find_token_range(s: &str) -> Option<(usize, usize)> {
let mut start = None;
for (idx, ch) in s.char_indices() {
if ch.is_whitespace() || ch == '{' {
if let Some(start) = start {
return Some((start, idx));
}
} else if start.is_none() {
start = Some(idx);
}
}
if let Some(start) = start {
return Some((start, s.len()));
}
None
}
#[cfg(test)]
mod test {
use super::{find_token_range, find_type_name_range, find_type_range};
#[test]
fn extract_type_part_range() {
for (actual, expect) in [
("{t1}", Some("{t1}")),
(" { t2 } ", Some("{ t2 }")),
("x{{ t3: string }}x", None),
("{t4} name", Some("{t4}")),
(" {t5} ", Some("{t5}")),
("{t6 x", None),
("t7", None),
("{{t8}", None),
("", None),
("entries Description {@link Type}", None),
("name See {@link Foo} and {@link Bar}", None),
("bar - With braces {}", None),
("{[ true, false ]}", Some("{[ true, false ]}")),
(
"{{
t9a: string;
t9b: number;
}}",
Some("{{\nt9a: string;\nt9b: number;\n}}"),
),
] {
assert_eq!(find_type_range(actual).map(|(s, e)| &actual[s..e]), expect);
}
}
#[test]
fn extract_type_name_part_range() {
for (actual, expect) in [
("", None),
("n1", Some("n1")),
(" n2 ", Some("n2")),
(" n3 n3", Some("n3")),
("[n4]\n", Some("[n4]")),
("[n5 = 1]", Some("[n5 = 1]")),
(" [n6 = [1,[2, [3]]]] ", Some("[n6 = [1,[2, [3]]]]")),
(r#"[n7 = "foo bar"]"#, Some(r#"[n7 = "foo bar"]"#)),
("n.n8", Some("n.n8")),
("n[].n9", Some("n[].n9")),
(r#"[ n10 = ["{}", "[]"] ]"#, Some(r#"[ n10 = ["{}", "[]"] ]"#)),
("[n11... c11]", Some("[n11... c11]")),
("[n12[]\nc12]", Some("[n12[]\nc12]")),
("n12.n12", Some("n12.n12")),
("n13[].n13", Some("n13[].n13")),
("[n12[]\nc12", None),
("[n12\nc12", None),
] {
assert_eq!(find_type_name_range(actual).map(|(s, e)| &actual[s..e]), expect);
}
}
#[test]
fn extract_token_part_range() {
for (actual, expect) in [
("k1", Some("k1")),
("k2 x", Some("k2")),
(" k3 ", Some("k3")),
("k4\ny", Some("k4")),
("", None),
(" トークン5\n", Some("トークン5")),
("\nk6\nx", Some("k6")),
("k7{", Some("k7")),
] {
assert_eq!(find_token_range(actual).map(|(s, e)| &actual[s..e]), expect);
}
}
}