Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/handlers/spelling.rs')
-rw-r--r--helix-view/src/handlers/spelling.rs128
1 files changed, 128 insertions, 0 deletions
diff --git a/helix-view/src/handlers/spelling.rs b/helix-view/src/handlers/spelling.rs
new file mode 100644
index 00000000..49fd7812
--- /dev/null
+++ b/helix-view/src/handlers/spelling.rs
@@ -0,0 +1,128 @@
+use std::{
+ borrow::Cow,
+ collections::{HashMap, HashSet},
+};
+
+use futures_util::{future::BoxFuture, FutureExt as _};
+use helix_core::{SpellingLanguage, Tendril, Transaction};
+use helix_event::{TaskController, TaskHandle};
+use tokio::sync::mpsc::Sender;
+
+use crate::{diagnostic::DiagnosticProvider, Action, DocumentId, Editor};
+
+const ACTION_PRIORITY: u8 = 0;
+
+#[derive(Debug)]
+pub struct SpellingHandler {
+ pub event_tx: Sender<SpellingEvent>,
+ pub requests: HashMap<DocumentId, TaskController>,
+ pub loading_dictionaries: HashSet<SpellingLanguage>,
+}
+
+impl SpellingHandler {
+ pub fn new(event_tx: Sender<SpellingEvent>) -> Self {
+ Self {
+ event_tx,
+ requests: Default::default(),
+ loading_dictionaries: Default::default(),
+ }
+ }
+
+ pub fn open_request(&mut self, document: DocumentId) -> TaskHandle {
+ let mut controller = TaskController::new();
+ let handle = controller.restart();
+ self.requests.insert(document, controller);
+ handle
+ }
+}
+
+#[derive(Debug)]
+pub enum SpellingEvent {
+ /*
+ DictionaryUpdated {
+ word: String,
+ language: SpellingLanguage,
+ },
+ */
+ DictionaryLoaded { language: SpellingLanguage },
+ DocumentOpened { doc: DocumentId },
+ DocumentChanged { doc: DocumentId },
+}
+
+impl Editor {
+ pub(crate) fn spelling_actions(
+ &self,
+ ) -> Option<BoxFuture<'static, anyhow::Result<Vec<Action>>>> {
+ let (view, doc) = current_ref!(self);
+ let doc_id = doc.id();
+ let view_id = view.id;
+ let language = doc.spelling_language()?;
+ // TODO: consider fixes for all selections?
+ let range = doc.selection(view_id).primary();
+ let text = doc.text().clone();
+ let dictionary = self.dictionaries.get(&language)?.clone();
+ // TODO: can do this faster with partition_point + take_while
+ let selected_diagnostics: Vec<_> = doc
+ .diagnostics()
+ .iter()
+ .filter(|d| {
+ range.overlaps(&helix_core::Range::new(d.range.start, d.range.end))
+ && d.inner.provider == DiagnosticProvider::Spelling
+ })
+ .map(|d| d.range)
+ .collect();
+
+ let future = tokio::task::spawn_blocking(move || {
+ let text = text.slice(..);
+ let dictionary = dictionary.read();
+ let mut suggest_buffer = Vec::new();
+ selected_diagnostics
+ .into_iter()
+ .flat_map(|range| {
+ suggest_buffer.clear();
+ let word = Cow::from(text.slice(range.start..range.end));
+ dictionary.suggest(&word, &mut suggest_buffer);
+
+ let mut actions = Vec::with_capacity(suggest_buffer.len() + 1);
+ actions.extend(
+ suggest_buffer.drain(..).map(|suggestion| {
+ Action::new(
+ format!("Replace '{word}' with '{suggestion}'"),
+ ACTION_PRIORITY,
+ move |editor| {
+ let doc = doc_mut!(editor, &doc_id);
+ let view = view_mut!(editor, view_id);
+ let transaction = Transaction::change(
+ doc.text(),
+ [(range.start, range.end, Some(Tendril::from(suggestion.as_str())))].into_iter(),
+ );
+ doc.apply(&transaction, view_id);
+ doc.append_changes_to_history(view);
+ // TODO: get rid of the diagnostic for this word.
+ },
+ )
+ })
+ );
+ let word = word.to_string();
+ actions.push(Action::new(
+ format!("Add '{word}' to dictionary '{language}'"),
+ ACTION_PRIORITY,
+ move |editor| {
+ let Some(dictionary) = editor.dictionaries.get(&language) else {
+ log::error!("Failed to add '{word}' to dictionary '{language}' because the dictionary does not exist");
+ return;
+ };
+ // TODO: fire an event?
+ let mut dictionary = dictionary.write();
+ if let Err(err) = dictionary.add(&word) {
+ log::error!("Failed to add '{word}' to dictionary '{language}': {err}");
+ }
+ }
+ ));
+ actions
+ })
+ .collect()
+ });
+ Some(async move { Ok(future.await?) }.boxed())
+ }
+}