Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix split_at (return Result ver.) #455

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 44 additions & 16 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,10 @@ impl<'a> Iterator for LinesWithEndings<'a> {
/// the `Vec<(Style, &str)>` returned by `highlight` methods. Look at the source
/// code for `modify_range` for an example usage.
#[allow(clippy::type_complexity)]
pub fn split_at<'a, A: Clone>(v: &[(A, &'a str)], split_i: usize) -> (Vec<(A, &'a str)>, Vec<(A, &'a str)>) {
pub fn split_at<'a, A: Clone>(
v: &[(A, &'a str)],
split_i: usize,
) -> Result<(Vec<(A, &'a str)>, Vec<(A, &'a str)>), std::fmt::Error> {
// This function works by gradually reducing the problem into smaller sub-problems from the front
let mut rest = v;
let mut rest_split_i = split_i;
Expand All @@ -247,15 +250,21 @@ pub fn split_at<'a, A: Clone>(v: &[(A, &'a str)], split_i: usize) -> (Vec<(A, &'
let mut after = Vec::new();
// If necessary, split the token the split falls inside
if !rest.is_empty() && rest_split_i > 0 {
let (sa, sb) = rest[0].1.split_at(rest_split_i);
before.push((rest[0].0.clone(), sa));
after.push((rest[0].0.clone(), sb));
rest = &rest[1..];
// Splitting in the middle of a multibyte character cause panic.
// To avoid it, this function may return Err.
if !rest[0].1.is_char_boundary(rest_split_i) {
return Err(std::fmt::Error);
} else {
let (sa, sb) = rest[0].1.split_at(rest_split_i);
before.push((rest[0].0.clone(), sa));
after.push((rest[0].0.clone(), sb));
rest = &rest[1..];
}
}

after.extend_from_slice(rest);

(before, after)
Ok((before, after))
}

/// Modify part of a highlighted line using a style modifier, useful for highlighting sections of a line.
Expand All @@ -274,13 +283,17 @@ pub fn split_at<'a, A: Clone>(v: &[(A, &'a str)], split_i: usize) -> (Vec<(A, &'
/// let l2 = modify_range(l, 1..6, boldmod);
/// assert_eq!(l2, &[(plain, "a"), (bold, "bc"), (bold, "def"), (plain, "ghi")]);
/// ```
pub fn modify_range<'a>(v: &[(Style, &'a str)], r: Range<usize>, modifier: StyleModifier) -> Vec<(Style, &'a str)> {
let (mut result, in_and_after) = split_at(v, r.start);
let (inside, mut after) = split_at(&in_and_after, r.end - r.start);
pub fn modify_range<'a>(
v: &[(Style, &'a str)],
r: Range<usize>,
modifier: StyleModifier,
) -> Result<Vec<(Style, &'a str)>, std::fmt::Error> {
let (mut result, in_and_after) = split_at(v, r.start)?;
let (inside, mut after) = split_at(&in_and_after, r.end - r.start)?;

result.extend(inside.iter().map(|(style, s)| { (style.apply(modifier), *s)}));
result.append(&mut after);
result
Ok(result)
}

#[cfg(test)]
Expand Down Expand Up @@ -309,25 +322,40 @@ mod tests {
#[test]
fn test_split_at() {
let l: &[(u8, &str)] = &[];
let (before, after) = split_at(l, 0); // empty
let (before, after) = split_at(l, 0).unwrap(); // empty
assert_eq!((&before[..], &after[..]), (&[][..],&[][..]));

let l = &[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")];

let (before, after) = split_at(l, 0); // at start
let (before, after) = split_at(l, 0).unwrap(); // at start
assert_eq!((&before[..], &after[..]), (&[][..],&[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")][..]));

let (before, after) = split_at(l, 4); // inside token
let (before, after) = split_at(l, 4).unwrap(); // inside token
assert_eq!((&before[..], &after[..]), (&[(0u8, "abc"), (1u8, "d")][..],&[(1u8, "ef"), (2u8, "ghi")][..]));

let (before, after) = split_at(l, 3); // between tokens
let (before, after) = split_at(l, 3).unwrap(); // between tokens
assert_eq!((&before[..], &after[..]), (&[(0u8, "abc")][..],&[(1u8, "def"), (2u8, "ghi")][..]));

let (before, after) = split_at(l, 9); // just after last token
let (before, after) = split_at(l, 9).unwrap(); // just after last token
assert_eq!((&before[..], &after[..]), (&[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")][..], &[][..]));

let (before, after) = split_at(l, 10); // out of bounds
let (before, after) = split_at(l, 10).unwrap(); // out of bounds
assert_eq!((&before[..], &after[..]), (&[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")][..], &[][..]));

let l = &[(0u8, "こんにちは"), (1u8, "世界"), (2u8, "!")];

let (before, after) = split_at(l, 3).unwrap();

assert_eq!(
(&before[..], &after[..]),
(
&[(0u8, "こ")][..],
&[(0u8, "んにちは"), (1u8, "世界"), (2u8, "!")][..]
)
);

//Splitting in the middle of a multibyte character returns Err
assert!(split_at(l, 4).is_err());
}

#[test]
Expand Down