lsp_core/systems/
prefix.rs
1use std::{collections::HashSet, ops::Deref};
2
3use bevy_ecs::prelude::*;
4use lsp_types::{CompletionItemKind, Diagnostic, DiagnosticSeverity, TextDocumentItem, TextEdit};
5use tracing::{debug, instrument};
6
7use crate::prelude::*;
8
9const JSONLD: &'static str = include_str!("./jsonld.json");
10
11lazy_static::lazy_static! {
12 static ref HASHMAP: Vec<(&'static str, &'static str)> = {
13 let m: Vec<_> = JSONLD.split('\n').flat_map(|x| { let mut s = x.split(' ');
14 let first = s.next()?;
15 let second = s.next()?;
16 Some((first, second))
17 }).collect();
18 m
19 };
20}
21
22#[derive(Debug, Clone)]
24pub struct Prefix {
25 pub prefix: String,
26 pub url: lsp_types::Url,
27}
28
29#[derive(Component, Debug)]
37pub struct Prefixes(pub Vec<Prefix>, pub lsp_types::Url);
38impl Deref for Prefixes {
39 type Target = Vec<Prefix>;
40
41 fn deref(&self) -> &Self::Target {
42 &self.0
43 }
44}
45impl Prefixes {
46 pub fn shorten(&self, value: &str) -> Option<String> {
47 let try_shorten = |prefix: &Prefix| {
48 let short = value.strip_prefix(prefix.url.as_str())?;
49 Some(format!("{}:{}", prefix.prefix, short))
50 };
51
52 self.0.iter().flat_map(try_shorten).next()
53 }
54
55 pub fn expand(&self, token: &Token) -> Option<String> {
56 match token {
57 Token::PNameLN(pref, x) => {
58 let pref = pref.as_ref().map(|x| x.as_str()).unwrap_or("");
59 let prefix = self.0.iter().find(|x| &x.prefix == pref)?;
60 Some(format!("{}{}", prefix.url, x))
61 }
62 Token::IRIRef(x) => {
63 return self.1.join(&x).ok().map(|x| x.to_string());
64 }
65 _ => None,
66 }
67 }
68
69 pub fn expand_json(&self, token: &Token) -> Option<String> {
70 match token {
71 Token::Str(pref, _) => {
72 if let Some(x) = pref.find(':') {
73 let prefix = &pref[..x];
74 if let Some(exp) = self.0.iter().find(|x| &x.prefix == prefix) {
75 return Some(format!("{}{}", exp.url.as_str(), &pref[x + 1..]));
76 }
77 } else {
78 if let Some(exp) = self.0.iter().find(|x| &x.prefix == pref) {
79 return Some(exp.url.as_str().to_string());
80 }
81 }
82
83 return Some(
84 self.1
85 .join(&pref)
86 .ok()
87 .map(|x| x.to_string())
88 .unwrap_or(pref.to_string()),
89 );
90 }
91 _ => None,
92 }
93 }
94}
95
96pub fn prefix_completion_helper(
97 word: &TokenComponent,
98 prefixes: &Prefixes,
99 completions: &mut Vec<SimpleCompletion>,
100 mut extra_edits: impl FnMut(&str, &str) -> Option<Vec<TextEdit>>,
101) {
102 match word.token.value() {
103 Token::Invalid(_) => {}
104 _ => return,
105 }
106
107 let mut defined = HashSet::new();
108 for p in prefixes.0.iter() {
109 defined.insert(p.url.as_str());
110 }
111
112 completions.extend(
113 HASHMAP
114 .iter()
115 .filter(|(name, _)| name.starts_with(&word.text))
116 .filter(|(_, location)| !defined.contains(location))
117 .flat_map(|(name, location)| {
118 let new_text = format!("{}:", name);
119 let sort_text = format!("2 {}", new_text);
120 let filter_text = new_text.clone();
121 if new_text != word.text {
122 let extra_edit = extra_edits(name, location)?;
123 let completion = SimpleCompletion::new(
124 CompletionItemKind::MODULE,
125 format!("{}", name),
126 lsp_types::TextEdit {
127 new_text,
128 range: word.range.clone(),
129 },
130 )
131 .sort_text(sort_text)
132 .filter_text(filter_text);
133
134 let completion = extra_edit
135 .into_iter()
136 .fold(completion, |completion: SimpleCompletion, edit| {
137 completion.text_edit(edit)
138 });
139 Some(completion)
140 } else {
141 None
142 }
143 }),
144 );
145}
146
147pub fn undefined_prefix(
148 query: Query<
149 (&Tokens, &Prefixes, &Wrapped<TextDocumentItem>, &RopeC),
150 Or<(Changed<Prefixes>, Changed<Tokens>)>,
151 >,
152 mut client: ResMut<DiagnosticPublisher>,
153) {
154 for (tokens, prefixes, item, rope) in &query {
155 let mut diagnostics: Vec<Diagnostic> = Vec::new();
156 for t in &tokens.0 {
157 match t.value() {
158 Token::PNameLN(x, _) => {
159 let pref = x.as_ref().map(|x| x.as_str()).unwrap_or("");
160 let found = prefixes.0.iter().find(|x| x.prefix == pref).is_some();
161 if !found {
162 if let Some(range) = range_to_range(t.span(), &rope) {
163 diagnostics.push(Diagnostic {
164 range,
165 severity: Some(DiagnosticSeverity::ERROR),
166 source: Some(String::from("SWLS")),
167 message: format!("Undefined prefix {}", pref),
168 related_information: None,
169 ..Default::default()
170 })
171 }
172 }
173 }
174 _ => {}
175 }
176 }
177 let _ = client.publish(&item.0, diagnostics, "undefined_prefix");
178 }
179}
180
181#[instrument(skip(query))]
182pub fn defined_prefix_completion(
183 mut query: Query<(&TokenComponent, &Prefixes, &mut CompletionRequest)>,
184) {
185 for (word, prefixes, mut req) in &mut query {
186 let st = &word.text;
187 let pref = if let Some(idx) = st.find(':') {
188 &st[..idx]
189 } else {
190 &st
191 };
192
193 debug!("matching {}", pref);
194
195 let completions = prefixes
196 .0
197 .iter()
198 .filter(|p| p.prefix.as_str().starts_with(pref))
199 .flat_map(|x| {
200 let new_text = format!("{}:", x.prefix.as_str());
201 if new_text != word.text {
202 Some(
203 SimpleCompletion::new(
204 CompletionItemKind::MODULE,
205 format!("{}", x.prefix.as_str()),
206 lsp_types::TextEdit {
207 new_text,
208 range: word.range.clone(),
209 },
210 )
211 .documentation(x.url.as_str()),
212 )
213 } else {
214 None
215 }
216 });
217
218 req.0.extend(completions);
219 }
220}