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