Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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())
    }
}