use inset_embedder::{Locale, TextRange};
use inset_foundation::App;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SuggestionSpan {
pub range: TextRange,
pub suggestions: Vec<String>,
}
impl SuggestionSpan {
pub fn new(range: TextRange, suggestions: Vec<String>) -> SuggestionSpan {
SuggestionSpan { range, suggestions }
}
}
impl std::fmt::Display for SuggestionSpan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"SuggestionSpan(range: {:?}, suggestions: {:?})",
self.range, self.suggestions
)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SpellCheckResults {
pub spell_checked_text: String,
pub suggestion_spans: Vec<SuggestionSpan>,
}
impl SpellCheckResults {
pub fn new(
spell_checked_text: impl Into<String>,
suggestion_spans: Vec<SuggestionSpan>,
) -> SpellCheckResults {
SpellCheckResults {
spell_checked_text: spell_checked_text.into(),
suggestion_spans,
}
}
}
impl std::fmt::Display for SpellCheckResults {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"SpellCheckResults(spellCheckText: {}, suggestionSpans: {:?})",
self.spell_checked_text, self.suggestion_spans
)
}
}
pub trait SpellCheckService {
fn fetch_spell_check_suggestions(
&self,
app: &App,
locale: &Locale,
text: &str,
) -> Option<Vec<SuggestionSpan>>;
}
#[derive(Clone, Debug, Default)]
pub struct DefaultSpellCheckService {
pub last_saved_results: Option<SpellCheckResults>,
}
impl DefaultSpellCheckService {
pub fn new() -> DefaultSpellCheckService {
DefaultSpellCheckService::default()
}
pub fn merge_results(
old_results: &[SuggestionSpan],
new_results: &[SuggestionSpan],
) -> Vec<SuggestionSpan> {
let mut merged_results = Vec::new();
let mut old_span_pointer = 0;
let mut new_span_pointer = 0;
while old_span_pointer < old_results.len() && new_span_pointer < new_results.len() {
let old_span = &old_results[old_span_pointer];
let new_span = &new_results[new_span_pointer];
if old_span.range.start == new_span.range.start {
merged_results.push(old_span.clone());
old_span_pointer += 1;
new_span_pointer += 1;
} else if old_span.range.start < new_span.range.start {
merged_results.push(old_span.clone());
old_span_pointer += 1;
} else {
merged_results.push(new_span.clone());
new_span_pointer += 1;
}
}
merged_results.extend_from_slice(&old_results[old_span_pointer..]);
merged_results.extend_from_slice(&new_results[new_span_pointer..]);
merged_results
}
}
impl SpellCheckService for DefaultSpellCheckService {
fn fetch_spell_check_suggestions(
&self,
app: &App,
locale: &Locale,
text: &str,
) -> Option<Vec<SuggestionSpan>> {
let _ = (app, locale, text);
None
}
}
#[cfg(test)]
mod tests {
use inset_foundation::AppCell;
use super::*;
#[test]
fn suggestion_span_equality() {
let span = SuggestionSpan::new(
TextRange::new(7, 12),
vec!["word".into(), "world".into(), "old".into()],
);
assert_eq!(
span,
SuggestionSpan::new(
TextRange::new(7, 12),
vec!["word".into(), "world".into(), "old".into()],
)
);
assert_ne!(
span,
SuggestionSpan::new(TextRange::new(7, 12), vec!["world".into()])
);
}
#[test]
fn spell_check_results_equality() {
let span = SuggestionSpan::new(TextRange::new(7, 12), vec!["world".into()]);
let results = SpellCheckResults::new("Hello, wrold!", vec![span.clone()]);
assert_eq!(results, SpellCheckResults::new("Hello, wrold!", vec![span]));
assert_ne!(results, SpellCheckResults::new("Hello, world!", Vec::new()));
}
#[test]
fn merge_results_keeps_old_span_on_same_start() {
let old = vec![SuggestionSpan::new(
TextRange::new(0, 4),
vec!["old".into()],
)];
let new = vec![SuggestionSpan::new(
TextRange::new(0, 4),
vec!["new".into()],
)];
let merged = DefaultSpellCheckService::merge_results(&old, &new);
assert_eq!(merged, old);
}
#[test]
fn default_service_is_unsupported() {
let cell = AppCell::new();
let app = cell.borrow_mut();
let service = DefaultSpellCheckService::new();
assert!(
service
.fetch_spell_check_suggestions(&app, &Locale::new("en"), "wrold")
.is_none()
);
}
}