inset_services/
spell_check.rs1use inset_embedder::{Locale, TextRange};
4use inset_foundation::App;
5
6#[derive(Clone, Debug, PartialEq, Eq, Hash)]
22pub struct SuggestionSpan {
23 pub range: TextRange,
25 pub suggestions: Vec<String>,
27}
28
29impl SuggestionSpan {
30 pub fn new(range: TextRange, suggestions: Vec<String>) -> SuggestionSpan {
33 SuggestionSpan { range, suggestions }
34 }
35}
36
37impl std::fmt::Display for SuggestionSpan {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 write!(
40 f,
41 "SuggestionSpan(range: {:?}, suggestions: {:?})",
42 self.range, self.suggestions
43 )
44 }
45}
46
47#[derive(Clone, Debug, PartialEq, Eq, Hash)]
50pub struct SpellCheckResults {
51 pub spell_checked_text: String,
53 pub suggestion_spans: Vec<SuggestionSpan>,
60}
61
62impl SpellCheckResults {
63 pub fn new(
65 spell_checked_text: impl Into<String>,
66 suggestion_spans: Vec<SuggestionSpan>,
67 ) -> SpellCheckResults {
68 SpellCheckResults {
69 spell_checked_text: spell_checked_text.into(),
70 suggestion_spans,
71 }
72 }
73}
74
75impl std::fmt::Display for SpellCheckResults {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 write!(
78 f,
79 "SpellCheckResults(spellCheckText: {}, suggestionSpans: {:?})",
80 self.spell_checked_text, self.suggestion_spans
81 )
82 }
83}
84
85pub trait SpellCheckService {
87 fn fetch_spell_check_suggestions(
96 &self,
97 app: &App,
98 locale: &Locale,
99 text: &str,
100 ) -> Option<Vec<SuggestionSpan>>;
101}
102
103#[derive(Clone, Debug, Default)]
115pub struct DefaultSpellCheckService {
116 pub last_saved_results: Option<SpellCheckResults>,
118}
119
120impl DefaultSpellCheckService {
121 pub fn new() -> DefaultSpellCheckService {
123 DefaultSpellCheckService::default()
124 }
125
126 pub fn merge_results(
135 old_results: &[SuggestionSpan],
136 new_results: &[SuggestionSpan],
137 ) -> Vec<SuggestionSpan> {
138 let mut merged_results = Vec::new();
139
140 let mut old_span_pointer = 0;
141 let mut new_span_pointer = 0;
142
143 while old_span_pointer < old_results.len() && new_span_pointer < new_results.len() {
144 let old_span = &old_results[old_span_pointer];
145 let new_span = &new_results[new_span_pointer];
146
147 if old_span.range.start == new_span.range.start {
148 merged_results.push(old_span.clone());
149 old_span_pointer += 1;
150 new_span_pointer += 1;
151 } else if old_span.range.start < new_span.range.start {
152 merged_results.push(old_span.clone());
153 old_span_pointer += 1;
154 } else {
155 merged_results.push(new_span.clone());
156 new_span_pointer += 1;
157 }
158 }
159
160 merged_results.extend_from_slice(&old_results[old_span_pointer..]);
161 merged_results.extend_from_slice(&new_results[new_span_pointer..]);
162
163 merged_results
164 }
165}
166
167impl SpellCheckService for DefaultSpellCheckService {
168 fn fetch_spell_check_suggestions(
169 &self,
170 app: &App,
171 locale: &Locale,
172 text: &str,
173 ) -> Option<Vec<SuggestionSpan>> {
174 let _ = (app, locale, text);
175 None
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use inset_foundation::AppCell;
182
183 use super::*;
184
185 #[test]
186 fn suggestion_span_equality() {
187 let span = SuggestionSpan::new(
188 TextRange::new(7, 12),
189 vec!["word".into(), "world".into(), "old".into()],
190 );
191 assert_eq!(
192 span,
193 SuggestionSpan::new(
194 TextRange::new(7, 12),
195 vec!["word".into(), "world".into(), "old".into()],
196 )
197 );
198 assert_ne!(
199 span,
200 SuggestionSpan::new(TextRange::new(7, 12), vec!["world".into()])
201 );
202 }
203
204 #[test]
205 fn spell_check_results_equality() {
206 let span = SuggestionSpan::new(TextRange::new(7, 12), vec!["world".into()]);
207 let results = SpellCheckResults::new("Hello, wrold!", vec![span.clone()]);
208 assert_eq!(results, SpellCheckResults::new("Hello, wrold!", vec![span]));
209 assert_ne!(results, SpellCheckResults::new("Hello, world!", Vec::new()));
210 }
211
212 #[test]
213 fn merge_results_keeps_old_span_on_same_start() {
214 let old = vec![SuggestionSpan::new(
215 TextRange::new(0, 4),
216 vec!["old".into()],
217 )];
218 let new = vec![SuggestionSpan::new(
219 TextRange::new(0, 4),
220 vec!["new".into()],
221 )];
222 let merged = DefaultSpellCheckService::merge_results(&old, &new);
223 assert_eq!(merged, old);
224 }
225
226 #[test]
227 fn default_service_is_unsupported() {
228 let cell = AppCell::new();
229 let app = cell.borrow_mut();
230 let service = DefaultSpellCheckService::new();
231 assert!(
232 service
233 .fetch_spell_check_suggestions(&app, &Locale::new("en"), "wrold")
234 .is_none()
235 );
236 }
237}