<commit_before>// Copyright 2020 The Evcxr Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::code_block::count_columns;
use crate::code_block::CodeBlock;
use crate::code_block::CodeKind;
use crate::code_block::CommandCall;
use crate::code_block::Segment;
use crate::code_block::UserCodeInfo;
use ariadne::Color;
use ariadne::{ColorGenerator, Label, Report, ReportKind};
use json::JsonValue;
use json::{self};
use once_cell::sync::OnceCell;
use ra_ap_ide::TextRange;
use ra_ap_ide::TextSize;
use regex::Regex;
use std::fmt;
use std::fmt::Write as _;
use std::io;
use std::ops::Range;

#[derive(Debug, Clone)]
pub struct CompilationError {
    message: String,
    pub json: JsonValue,
    pub(crate) code_origins: Vec<CodeKind>,
    spanned_messages: Vec<SpannedMessage>,
    spanned_helps: Vec<SpannedMessage>,
    level: String,
}

pub enum Theme {
    Light,
    Dark,
}

fn span_to_byte_range(source: &str, span: &Span) -> Range<usize> {
    fn line_and_number_to_byte_offset(source: &str, line_number: usize, column: usize) -> usize {
        source
            .lines()
            .take(line_number - 1)
            .map(|x| x.len())
            .sum::<usize>()
            + column
            + line_number
            - 2
    }
    line_and_number_to_byte_offset(source, span.start_line, span.start_column)
        ..line_and_number_to_byte_offset(source, span.end_line, span.end_column)
}

impl CompilationError {
    pub fn build_report(
        &self,
        file_name: String,
        source: String,
        theme: Theme,
    ) -> Option<Report<(String, Range<usize>)>> {
        let error = self;
        if !source.is_ascii() {
            return None;
        }
        let mut builder =
            Report::build(ReportKind::Error, file_name.clone(), 0).with_message(&error.message());
        let mut next_color = {
            let mut colors = ColorGenerator::new();
            move || {
                if let Color::Fixed(x) = colors.next() {
                    Color::Fixed(match theme {
                        Theme::Light => 255 - x,
                        Theme::Dark => x,
                    })
                } else {
                    unreachable!()
                }
            }
        };
        if let Some(code) = error.code() {
            builder = builder.with_code(code);
        }
        for spanned_message in error
            .spanned_messages()
            .iter()
            .chain(error.help_spanned().iter())
        {
            if let Some(span) = &spanned_message.span {
                if spanned_message.label.is_empty() {
                    continue;
                }
                builder = builder.with_label(
                    Label::new((file_name.clone(), span_to_byte_range(&source, span)))
                        .with_message(&spanned_message.label)
                        .with_color(next_color())
                        .with_order(10),
                );
            } else {
                return None;
            }
        }
        Some(builder.finish())
    }
}

fn spans_in_local_source(span: &JsonValue) -> Option<&JsonValue> {
    if let Some(file_name) = span["file_name"].as_str() {
        if file_name.ends_with("lib.rs") {
            return Some(span);
        }
    }
    let expansion = &span["expansion"];<commit_msg>Still use ariadne to render errors when there's non-spanned messages<commit_after>// Copyright 2020 The Evcxr Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::code_block::count_columns;
use crate::code_block::CodeBlock;
use crate::code_block::CodeKind;
use crate::code_block::CommandCall;
use crate::code_block::Segment;
use crate::code_block::UserCodeInfo;
use ariadne::Color;
use ariadne::{ColorGenerator, Label, Report, ReportKind};
use json::JsonValue;
use json::{self};
use once_cell::sync::OnceCell;
use ra_ap_ide::TextRange;
use ra_ap_ide::TextSize;
use regex::Regex;
use std::fmt;
use std::fmt::Write as _;
use std::io;
use std::ops::Range;

#[derive(Debug, Clone)]
pub struct CompilationError {
    message: String,
    pub json: JsonValue,
    pub(crate) code_origins: Vec<CodeKind>,
    spanned_messages: Vec<SpannedMessage>,
    spanned_helps: Vec<SpannedMessage>,
    level: String,
}

pub enum Theme {
    Light,
    Dark,
}

fn span_to_byte_range(source: &str, span: &Span) -> Range<usize> {
    fn line_and_number_to_byte_offset(source: &str, line_number: usize, column: usize) -> usize {
        source
            .lines()
            .take(line_number - 1)
            .map(|x| x.len())
            .sum::<usize>()
            + column
            + line_number
            - 2
    }
    line_and_number_to_byte_offset(source, span.start_line, span.start_column)
        ..line_and_number_to_byte_offset(source, span.end_line, span.end_column)
}

impl CompilationError {
    pub fn build_report(
        &self,
        file_name: String,
        source: String,
        theme: Theme,
    ) -> Option<Report<(String, Range<usize>)>> {
        let error = self;
        if !source.is_ascii() {
            return None;
        }
        let mut builder =
            Report::build(ReportKind::Error, file_name.clone(), 0).with_message(&error.message());
        let mut next_color = {
            let mut colors = ColorGenerator::new();
            move || {
                if let Color::Fixed(x) = colors.next() {
                    Color::Fixed(match theme {
                        Theme::Light => 255 - x,
                        Theme::Dark => x,
                    })
                } else {
                    unreachable!()
                }
            }
        };
        if let Some(code) = error.code() {
            builder = builder.with_code(code);
        }
        let mut notes = String::new();
        for spanned_message in error
            .spanned_messages()
            .iter()
            .chain(error.help_spanned().iter())
        {
            if let Some(span) = &spanned_message.span {
                if spanned_message.label.is_empty() {
                    continue;
                }
                builder = builder.with_label(
                    Label::new((file_name.clone(), span_to_byte_range(&source, span)))
                        .with_message(&spanned_message.label)
                        .with_color(next_color())
                        .with_order(10),
                );
            } else {
                notes.push_str(&spanned_message.label);
            }
        }
        if !notes.is_empty() {
            builder.set_note(notes);
        }
        Some(builder.finish())
    }
}

fn spans_in_local_source(span: &JsonValue) -> Option<&JsonValue> {
    if let Some(file_name) = span["file_name"].as_str() {
        if file_name.ends_with("lib.rs") {
            return Some(span);
        }
    }
    let expansion = &span["expansion"];
{'fraction_non_alphanumeric': 0.11783318567269965, 'fraction_numerical': 0.004050120237944564, 'mean_word_length': 2.06993006993007, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 33, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7348537', 'n_tokens_mistral': 2327, 'n_tokens_neox': 2269, 'n_words': 560}