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

Add helpers for modifying highlighted lines #198

Merged
merged 4 commits into from
Aug 22, 2018
Merged
Show file tree
Hide file tree
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
10 changes: 10 additions & 0 deletions src/highlighting/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ impl Style {
}
}

impl Default for Style {
fn default() -> Style {
Style {
foreground: Color::BLACK,
background: Color::WHITE,
font_style: FontStyle::empty(),
}
}
}

impl StyleModifier {
/// Applies the other modifier to this one, creating a new modifier.
/// Values in `other` are preferred.
Expand Down
95 changes: 92 additions & 3 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! Convenient utility methods, mostly for printing `syntect` data structures
//! prettily to the terminal.
use highlighting::Style;
//! Convenient helper functions for common use cases:
//! printing to terminal, iterating lines with `\n`s, modifying ranges of highlighted output
use highlighting::{Style, StyleModifier};
use std::fmt::Write;
use std::ops::Range;
#[cfg(feature = "parsing")]
use parsing::ScopeStackOp;

Expand Down Expand Up @@ -112,6 +113,70 @@ impl<'a> Iterator for LinesWithEndings<'a> {
}
}

/// Split a highlighted line at a byte index in the line into a before and
/// after component. It's just a helper that does the somewhat tricky logic
/// including splitting a span if the index lies on a boundary.
///
/// This can be used to extract a chunk of the line out for special treatment
/// like wrapping it in an HTML tag for extra styling.
///
/// Generic for testing purposes and fancier use cases, but intended for use with
/// the `Vec<(Style, &str)>` returned by `highlight` methods. Look at the source
/// code for `modify_range` for an example usage.
pub fn split_at<'a, A: Clone>(v: &[(A, &'a str)], split_i: usize) -> (Vec<(A, &'a str)>, Vec<(A, &'a str)>) {
// 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;

// Consume all tokens before the split
let mut before = Vec::new();
for tok in rest { // Use for instead of a while to avoid bounds checks
if tok.1.len() > rest_split_i {
break;
}
before.push(tok.clone());
rest_split_i -= tok.1.len();
}
rest = &rest[before.len()..];

let mut after = Vec::new();
// If necessary, split the token the split falls inside
if rest.len() > 0 && 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..];
}

after.extend_from_slice(rest);

return (before, after);
}

/// Modify part of a highlighted line using a style modifier, useful for highlighting sections of a line.
///
/// # Examples
///
/// ```
/// use syntect::util::modify_range;
/// use syntect::highlighting::{Style, StyleModifier, FontStyle};
///
/// let plain = Style::default();
/// let boldmod = StyleModifier { foreground: None, background: None, font_style: Some(FontStyle::BOLD) };
/// let bold = plain.apply(boldmod);
///
/// let l = &[(plain, "abc"), (plain, "def"), (plain, "ghi")];
/// 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);

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

#[cfg(test)]
mod tests {
Expand All @@ -134,4 +199,28 @@ mod tests {
assert_eq!(lines("\nfoo"), vec!["\n", "foo"]);
assert_eq!(lines("\n\n\n"), vec!["\n", "\n", "\n"]);
}

#[test]
fn test_split_at() {
let l: &[(u8, &str)] = &[];
let (before, after) = split_at(l, 0); // empty
assert_eq!((&before[..], &after[..]), (&[][..],&[][..]));

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

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

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

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

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

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