use std::{collections::HashSet, ops::Deref};
use bevy_ecs::prelude::*;
use lsp_types::{CompletionItemKind, Diagnostic, DiagnosticSeverity, TextDocumentItem, TextEdit};
use tracing::{debug, instrument};
use crate::prelude::*;
const JSONLD: &'static str = include_str!("./jsonld.json");
lazy_static::lazy_static! {
static ref HASHMAP: Vec<(&'static str, &'static str)> = {
let m: Vec<_> = JSONLD.split('\n').flat_map(|x| { let mut s = x.split(' ');
let first = s.next()?;
let second = s.next()?;
Some((first, second))
}).collect();
m
};
}
#[derive(Debug, Clone)]
pub struct Prefix {
pub prefix: String,
pub url: lsp_types::Url,
}
#[derive(Component, Debug)]
pub struct Prefixes(pub Vec<Prefix>, pub lsp_types::Url);
impl Deref for Prefixes {
type Target = Vec<Prefix>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Prefixes {
pub fn shorten(&self, value: &str) -> Option<String> {
let try_shorten = |prefix: &Prefix| {
let short = value.strip_prefix(prefix.url.as_str())?;
Some(format!("{}:{}", prefix.prefix, short))
};
self.0.iter().flat_map(try_shorten).next()
}
pub fn expand(&self, token: &Token) -> Option<String> {
match token {
Token::PNameLN(pref, x) => {
let pref = pref.as_ref().map(|x| x.as_str()).unwrap_or("");
let prefix = self.0.iter().find(|x| &x.prefix == pref)?;
Some(format!("{}{}", prefix.url, x))
}
Token::IRIRef(x) => {
return self.1.join(&x).ok().map(|x| x.to_string());
}
_ => None,
}
}
pub fn expand_json(&self, token: &Token) -> Option<String> {
match token {
Token::Str(pref, _) => {
if let Some(x) = pref.find(':') {
let prefix = &pref[..x];
if let Some(exp) = self.0.iter().find(|x| &x.prefix == prefix) {
return Some(format!("{}{}", exp.url.as_str(), &pref[x + 1..]));
}
} else {
if let Some(exp) = self.0.iter().find(|x| &x.prefix == pref) {
return Some(exp.url.as_str().to_string());
}
}
return Some(
self.1
.join(&pref)
.ok()
.map(|x| x.to_string())
.unwrap_or(pref.to_string()),
);
}
_ => None,
}
}
}
pub fn prefix_completion_helper(
word: &TokenComponent,
prefixes: &Prefixes,
completions: &mut Vec<SimpleCompletion>,
mut extra_edits: impl FnMut(&str, &str) -> Option<Vec<TextEdit>>,
) {
match word.token.value() {
Token::Invalid(_) => {}
_ => return,
}
let mut defined = HashSet::new();
for p in prefixes.0.iter() {
defined.insert(p.url.as_str());
}
completions.extend(
HASHMAP
.iter()
.filter(|(name, _)| name.starts_with(&word.text))
.filter(|(_, location)| !defined.contains(location))
.flat_map(|(name, location)| {
let new_text = format!("{}:", name);
let sort_text = format!("2 {}", new_text);
let filter_text = new_text.clone();
if new_text != word.text {
let extra_edit = extra_edits(name, location)?;
let completion = SimpleCompletion::new(
CompletionItemKind::MODULE,
format!("{}", name),
lsp_types::TextEdit {
new_text,
range: word.range.clone(),
},
)
.sort_text(sort_text)
.filter_text(filter_text);
let completion = extra_edit
.into_iter()
.fold(completion, |completion: SimpleCompletion, edit| {
completion.text_edit(edit)
});
Some(completion)
} else {
None
}
}),
);
}
pub fn undefined_prefix(
query: Query<
(&Tokens, &Prefixes, &Wrapped<TextDocumentItem>, &RopeC),
Or<(Changed<Prefixes>, Changed<Tokens>)>,
>,
mut client: ResMut<DiagnosticPublisher>,
) {
for (tokens, prefixes, item, rope) in &query {
let mut diagnostics: Vec<Diagnostic> = Vec::new();
for t in &tokens.0 {
match t.value() {
Token::PNameLN(x, _) => {
let pref = x.as_ref().map(|x| x.as_str()).unwrap_or("");
let found = prefixes.0.iter().find(|x| x.prefix == pref).is_some();
if !found {
if let Some(range) = range_to_range(t.span(), &rope) {
diagnostics.push(Diagnostic {
range,
severity: Some(DiagnosticSeverity::ERROR),
source: Some(String::from("SWLS")),
message: format!("Undefined prefix {}", pref),
related_information: None,
..Default::default()
})
}
}
}
_ => {}
}
}
let _ = client.publish(&item.0, diagnostics, "undefined_prefix");
}
}
#[instrument(skip(query))]
pub fn defined_prefix_completion(
mut query: Query<(&TokenComponent, &Prefixes, &mut CompletionRequest)>,
) {
for (word, prefixes, mut req) in &mut query {
let st = &word.text;
let pref = if let Some(idx) = st.find(':') {
&st[..idx]
} else {
&st
};
debug!("matching {}", pref);
let completions = prefixes
.0
.iter()
.filter(|p| p.prefix.as_str().starts_with(pref))
.flat_map(|x| {
let new_text = format!("{}:", x.prefix.as_str());
if new_text != word.text {
Some(
SimpleCompletion::new(
CompletionItemKind::MODULE,
format!("{}", x.prefix.as_str()),
lsp_types::TextEdit {
new_text,
range: word.range.clone(),
},
)
.documentation(x.url.as_str()),
)
} else {
None
}
});
req.0.extend(completions);
}
}