use std::collections::{HashMap, HashSet};
use bbnf::generate::prettify::hints::{self, closest_hint, is_valid_hint};
use ls_types::*;
use crate::analysis::LineIndex;
use super::types::{SemanticTokenInfo, token_types};
#[derive(Debug, Clone)]
pub struct PrettyInfo {
pub rule_name: String,
pub hints: Vec<String>,
pub span: (usize, usize),
pub rule_name_span: (usize, usize),
pub hint_spans: Vec<(usize, usize)>,
}
pub fn extract_pretties(
pretties: &[bbnf::types::PrettyDirective<'_>],
_src: &str,
) -> Vec<PrettyInfo> {
pretties
.iter()
.map(|p| {
let dir_src = p.span.as_str();
let dir_start = p.span.start;
let rule_name = p.rule_name.as_ref();
let rule_name_offset = dir_src
.find(rule_name)
.map(|off| off + dir_start)
.unwrap_or_else(|| {
panic!(
"could not resolve @pretty rule-name span for `{}` within directive `{}`",
rule_name, dir_src
)
});
let rule_name_span = (rule_name_offset, rule_name_offset + rule_name.len());
let mut search_start = rule_name_offset + rule_name.len() - dir_start;
let hint_spans: Vec<(usize, usize)> = p
.hints
.iter()
.map(|hint| {
let hint_str = hint.as_ref();
let offset = dir_src[search_start..]
.find(hint_str)
.map(|off| off + search_start + dir_start)
.unwrap_or_else(|| {
panic!(
"could not resolve @pretty hint span for `{}` within directive `{}`",
hint_str, dir_src
)
});
search_start = offset - dir_start + hint_str.len();
(offset, offset + hint_str.len())
})
.collect();
PrettyInfo {
rule_name: rule_name.to_string(),
hints: p.hints.iter().map(|h| h.to_string()).collect(),
span: (p.span.start, p.span.end),
rule_name_span,
hint_spans,
}
})
.collect()
}
pub fn validate_pretties(
pretties: &[PrettyInfo],
defined: &HashMap<&str, usize>,
imported_names: &HashSet<&str>,
line_index: &LineIndex,
) -> (Vec<Diagnostic>, Vec<SemanticTokenInfo>) {
let mut diagnostics = Vec::new();
let mut semantic_tokens = Vec::new();
for pretty in pretties {
semantic_tokens.push(SemanticTokenInfo {
span: (pretty.span.0, pretty.span.0 + 7),
token_type: token_types::KEYWORD,
});
semantic_tokens.push(SemanticTokenInfo {
span: pretty.rule_name_span,
token_type: token_types::RULE_REFERENCE,
});
if pretty.rule_name == "*" {
for (i, hint) in pretty.hints.iter().enumerate() {
let valid_modes = ["auto", "minimal", "off"];
if !valid_modes.contains(&hint.as_str()) {
let span = pretty
.hint_spans
.get(i)
.copied()
.unwrap_or_else(|| {
panic!(
"missing @pretty mode hint span for `{}` at index {}",
hint, i
)
});
diagnostics.push(Diagnostic {
range: line_index.span_to_range(span.0, span.1),
severity: Some(DiagnosticSeverity::WARNING),
source: Some("bbnf".into()),
message: format!(
"Unknown heuristic mode `{}`. Valid modes: auto, minimal, off",
hint
),
..Default::default()
});
}
if let Some(&span) = pretty.hint_spans.get(i) {
semantic_tokens.push(SemanticTokenInfo {
span,
token_type: token_types::KEYWORD,
});
}
}
continue;
}
if !defined.contains_key(pretty.rule_name.as_str())
&& !imported_names.contains(pretty.rule_name.as_str())
{
diagnostics.push(Diagnostic {
range: line_index.span_to_range(
pretty.rule_name_span.0,
pretty.rule_name_span.1,
),
severity: Some(DiagnosticSeverity::WARNING),
source: Some("bbnf".into()),
message: format!(
"`@pretty` targets undefined rule: `{}`",
pretty.rule_name
),
..Default::default()
});
}
for (i, hint) in pretty.hints.iter().enumerate() {
let span = pretty
.hint_spans
.get(i)
.copied()
.unwrap_or_else(|| {
panic!(
"missing @pretty hint span for `{}` at index {}",
hint, i
)
});
if !is_valid_hint(hint) {
let mut msg = format!("Unknown `@pretty` hint: `{}`", hint);
if let Some(suggestion) = closest_hint(hint) {
msg.push_str(&format!(". Did you mean `{}`?", suggestion));
} else {
let valid = hints::valid_hint_names();
msg.push_str(&format!(". Valid hints: {}", valid.join(", ")));
}
diagnostics.push(Diagnostic {
range: line_index.span_to_range(span.0, span.1),
severity: Some(DiagnosticSeverity::WARNING),
source: Some("bbnf".into()),
message: msg,
..Default::default()
});
}
semantic_tokens.push(SemanticTokenInfo {
span,
token_type: token_types::KEYWORD,
});
}
}
(diagnostics, semantic_tokens)
}