annotate-snippets 0.0.3

Library for building code annotations
Documentation
use super::*;
use snippet;

fn format_label(label: Option<&str>, style: Option<DisplayTextStyle>) -> Vec<DisplayTextFragment> {
    let mut result = vec![];
    if let Some(label) = label {
        let elements: Vec<&str> = label.split("__").collect();
        let mut idx = 0;
        for element in elements {
            let element_style = match style {
                Some(s) => s,
                None => if idx % 2 == 0 {
                    DisplayTextStyle::Regular
                } else {
                    DisplayTextStyle::Emphasis
                },
            };
            result.push(DisplayTextFragment {
                content: element.to_string(),
                style: element_style,
            });
            idx += 1;
        }
    }
    return result;
}

fn format_title(annotation: &snippet::Annotation) -> DisplayLine {
    let label = annotation.label.clone().unwrap_or("".to_string());
    DisplayLine::Raw(DisplayRawLine::Annotation {
        annotation: Annotation {
            annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
            id: annotation.id.clone(),
            label: format_label(Some(&label), Some(DisplayTextStyle::Emphasis)),
        },
        source_aligned: false,
        continuation: false,
    })
}

fn format_annotation(annotation: &snippet::Annotation) -> Vec<DisplayLine> {
    let mut result = vec![];
    let label = annotation.label.clone().unwrap_or("".to_string());
    for (i, line) in label.lines().enumerate() {
        result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
            annotation: Annotation {
                annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
                id: None,
                label: format_label(Some(line), None),
            },
            source_aligned: true,
            continuation: i != 0,
        }));
    }
    result
}

fn format_slice(slice: &snippet::Slice, is_first: bool, has_footer: bool) -> Vec<DisplayLine> {
    let mut body = format_body(slice, has_footer);
    let mut result = vec![];

    let header = format_header(slice, &body, is_first);
    if let Some(header) = header {
        result.push(header);
    }
    result.append(&mut body);
    result
}

fn format_header(
    slice: &snippet::Slice,
    body: &[DisplayLine],
    is_first: bool,
) -> Option<DisplayLine> {
    let main_annotation = slice.annotations.get(0);

    let display_header = if is_first {
        DisplayHeaderType::Initial
    } else {
        DisplayHeaderType::Continuation
    };

    if let Some(annotation) = main_annotation {
        let mut col = 1;
        let mut row = slice.line_start;

        for idx in 0..body.len() {
            if let DisplayLine::Source {
                line: DisplaySourceLine::Content { range, .. },
                ..
            } = body[idx]
            {
                if annotation.range.0 >= range.0 && annotation.range.0 <= range.1 {
                    col = annotation.range.0 - range.0;
                    break;
                }
                row += 1;
            }
        }
        if let Some(ref path) = slice.origin {
            return Some(DisplayLine::Raw(DisplayRawLine::Origin {
                path: path.to_string(),
                pos: Some((row, col)),
                header_type: display_header,
            }));
        }
    } else {
        if let Some(ref path) = slice.origin {
            return Some(DisplayLine::Raw(DisplayRawLine::Origin {
                path: path.to_string(),
                pos: None,
                header_type: display_header,
            }));
        }
    }
    None
}

fn fold_body(body: &[DisplayLine]) -> Vec<DisplayLine> {
    let mut new_body = vec![];

    let mut no_annotation_lines_counter = 0;
    let mut idx = 0;

    while idx < body.len() {
        match body[idx] {
            DisplayLine::Source {
                line: DisplaySourceLine::Annotation { .. },
                ref inline_marks,
                ..
            } => {
                if no_annotation_lines_counter > 2 {
                    let fold_start = idx - no_annotation_lines_counter;
                    let fold_end = idx;
                    let pre_len = if no_annotation_lines_counter > 8 {
                        4
                    } else {
                        0
                    };
                    let post_len = if no_annotation_lines_counter > 8 {
                        2
                    } else {
                        1
                    };
                    for i in fold_start..fold_start + pre_len {
                        new_body.push(body[i].clone());
                    }
                    new_body.push(DisplayLine::Fold {
                        inline_marks: inline_marks.clone(),
                    });
                    for i in fold_end - post_len..fold_end {
                        new_body.push(body[i].clone());
                    }
                } else {
                    let start = idx - no_annotation_lines_counter;
                    for i in start..idx {
                        new_body.push(body[i].clone());
                    }
                }
                no_annotation_lines_counter = 0;
            }
            DisplayLine::Source { .. } => {
                no_annotation_lines_counter += 1;
                idx += 1;
                continue;
            }
            _ => {
                no_annotation_lines_counter += 1;
            }
        }
        new_body.push(body[idx].clone());
        idx += 1;
    }

    return new_body;
}

fn format_body(slice: &snippet::Slice, has_footer: bool) -> Vec<DisplayLine> {
    let mut body = vec![];

    let mut current_line = slice.line_start;
    let mut current_index = 0;
    let mut line_index_ranges = vec![];

    for line in slice.source.lines() {
        let line_length = line.chars().count() + 1;
        let line_range = (current_index, current_index + line_length);
        body.push(DisplayLine::Source {
            lineno: Some(current_line),
            inline_marks: vec![],
            line: DisplaySourceLine::Content {
                text: line.to_string(),
                range: line_range,
            },
        });
        line_index_ranges.push(line_range);
        current_line += 1;
        current_index += line_length + 1;
    }

    let mut annotation_line_count = 0;
    let mut annotations = slice.annotations.clone();
    for idx in 0..body.len() {
        let (line_start, line_end) = line_index_ranges[idx];
        annotations.drain_filter(|annotation| {
            let body_idx = idx + annotation_line_count;
            let annotation_type = match annotation.annotation_type {
                snippet::AnnotationType::Error => DisplayAnnotationType::None,
                snippet::AnnotationType::Warning => DisplayAnnotationType::None,
                _ => DisplayAnnotationType::from(annotation.annotation_type),
            };
            match annotation.range {
                (start, _) if start > line_end => false,
                (start, end) if start >= line_start && end <= line_end + 1 => {
                    let range = (start - line_start, end - line_start);
                    body.insert(
                        body_idx + 1,
                        DisplayLine::Source {
                            lineno: None,
                            inline_marks: vec![],
                            line: DisplaySourceLine::Annotation {
                                annotation: Annotation {
                                    annotation_type,
                                    id: None,
                                    label: format_label(Some(&annotation.label), None),
                                },
                                range,
                                annotation_type: DisplayAnnotationType::from(
                                    annotation.annotation_type,
                                ),
                                annotation_part: DisplayAnnotationPart::Standalone,
                            },
                        },
                    );
                    annotation_line_count += 1;
                    true
                }
                (start, end) if start >= line_start && start <= line_end && end > line_end => {
                    if start - line_start == 0 {
                        if let DisplayLine::Source {
                            ref mut inline_marks,
                            ..
                        } = body[body_idx]
                        {
                            inline_marks.push(DisplayMark {
                                mark_type: DisplayMarkType::AnnotationStart,
                                annotation_type: DisplayAnnotationType::from(
                                    annotation.annotation_type,
                                ),
                            });
                        }
                    } else {
                        let range = (start - line_start, start - line_start + 1);
                        body.insert(
                            body_idx + 1,
                            DisplayLine::Source {
                                lineno: None,
                                inline_marks: vec![],
                                line: DisplaySourceLine::Annotation {
                                    annotation: Annotation {
                                        annotation_type: DisplayAnnotationType::None,
                                        id: None,
                                        label: vec![],
                                    },
                                    range,
                                    annotation_type: DisplayAnnotationType::from(
                                        annotation.annotation_type,
                                    ),
                                    annotation_part: DisplayAnnotationPart::MultilineStart,
                                },
                            },
                        );
                        annotation_line_count += 1;
                    }
                    false
                }
                (start, end) if start < line_start && end > line_end => {
                    if let DisplayLine::Source {
                        ref mut inline_marks,
                        ..
                    } = body[body_idx]
                    {
                        inline_marks.push(DisplayMark {
                            mark_type: DisplayMarkType::AnnotationThrough,
                            annotation_type: DisplayAnnotationType::from(
                                annotation.annotation_type,
                            ),
                        });
                    }
                    false
                }
                (start, end) if start < line_start && end >= line_start && end <= line_end => {
                    if let DisplayLine::Source {
                        ref mut inline_marks,
                        ..
                    } = body[body_idx]
                    {
                        inline_marks.push(DisplayMark {
                            mark_type: DisplayMarkType::AnnotationThrough,
                            annotation_type: DisplayAnnotationType::from(
                                annotation.annotation_type,
                            ),
                        });
                    }
                    let range = (end - line_start, end - line_start + 1);
                    body.insert(
                        body_idx + 1,
                        DisplayLine::Source {
                            lineno: None,
                            inline_marks: vec![DisplayMark {
                                mark_type: DisplayMarkType::AnnotationThrough,
                                annotation_type: DisplayAnnotationType::from(
                                    annotation.annotation_type,
                                ),
                            }],
                            line: DisplaySourceLine::Annotation {
                                annotation: Annotation {
                                    annotation_type,
                                    id: None,
                                    label: format_label(Some(&annotation.label), None),
                                },
                                range,
                                annotation_type: DisplayAnnotationType::from(
                                    annotation.annotation_type,
                                ),
                                annotation_part: DisplayAnnotationPart::MultilineEnd,
                            },
                        },
                    );
                    annotation_line_count += 1;
                    true
                }
                _ => false,
            }
        });
    }

    if slice.fold {
        body = fold_body(&body);
    }

    body.insert(
        0,
        DisplayLine::Source {
            lineno: None,
            inline_marks: vec![],
            line: DisplaySourceLine::Empty,
        },
    );
    if has_footer {
        body.push(DisplayLine::Source {
            lineno: None,
            inline_marks: vec![],
            line: DisplaySourceLine::Empty,
        });
    } else if let Some(DisplayLine::Source { .. }) = body.last() {
        body.push(DisplayLine::Source {
            lineno: None,
            inline_marks: vec![],
            line: DisplaySourceLine::Empty,
        });
    }
    body
}

impl From<snippet::Snippet> for DisplayList {
    fn from(snippet: snippet::Snippet) -> Self {
        let mut body = vec![];
        if let Some(annotation) = snippet.title {
            body.push(format_title(&annotation));
        }

        let mut slice_idx = 0;
        for slice in snippet.slices {
            body.append(&mut format_slice(
                &slice,
                slice_idx == 0,
                !snippet.footer.is_empty(),
            ));
            slice_idx += 1;
        }

        for annotation in snippet.footer {
            body.append(&mut format_annotation(&annotation));
        }

        Self { body }
    }
}

impl From<snippet::AnnotationType> for DisplayAnnotationType {
    fn from(at: snippet::AnnotationType) -> Self {
        match at {
            snippet::AnnotationType::Error => DisplayAnnotationType::Error,
            snippet::AnnotationType::Warning => DisplayAnnotationType::Warning,
            snippet::AnnotationType::Info => DisplayAnnotationType::Info,
            snippet::AnnotationType::Note => DisplayAnnotationType::Note,
            snippet::AnnotationType::Help => DisplayAnnotationType::Help,
        }
    }
}