Skip to main content

strop_engine/editor/resolution/
mod.rs

1//! Bounded input work: large grammar reads run on one CPU owner. Input after an
2//! accepted command stays ordered; Ctrl-C revokes it. Preview and execution use
3//! the same revision/cursor/command-stamped pure resolver result.
4mod worker;
5use super::{Editor, Key};
6use std::collections::VecDeque;
7use std::sync::{
8    atomic::{AtomicBool, Ordering},
9    mpsc, Arc,
10};
11use strop_core::{
12    id::{BufferRevision, DocumentId},
13    worker::{Completion, FailureKind, Outcome, Ticket},
14};
15use strop_grammar::{ActionPlan, Command, Resolved};
16
17#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
18pub struct ResolutionKey {
19    document: DocumentId,
20    revision: BufferRevision,
21    pane: usize,
22    command: Command,
23    cursors: Vec<usize>,
24    tab: usize,
25}
26#[derive(Clone, serde::Serialize, serde::Deserialize)]
27pub struct ResolutionData {
28    resolved: Vec<Option<Resolved>>,
29    plan: Option<ActionPlan>,
30    layouts: Vec<strop_core::layout::PreparedLineLayout>,
31}
32#[derive(serde::Serialize, serde::Deserialize)]
33pub enum ResolutionEvent {
34    Completed(Box<Completion<ResolutionKey, ResolutionData>>),
35    Stopped,
36}
37#[derive(Clone, Copy, PartialEq, Eq)]
38pub(crate) enum ResolutionPurpose {
39    Motion,
40    Execute,
41    IncSearch,
42    Preview,
43    VisualObject,
44    RepeatSearch(bool),
45    DotRepeat,
46}
47impl ResolutionPurpose {
48    fn blocks_input(self) -> bool {
49        matches!(
50            self,
51            Self::Motion
52                | Self::Execute
53                | Self::VisualObject
54                | Self::RepeatSearch(_)
55                | Self::DotRepeat
56        )
57    }
58}
59struct Pending {
60    ticket: Ticket<ResolutionKey>,
61    purpose: ResolutionPurpose,
62    cancel: Arc<AtomicBool>,
63    macro_depth: usize,
64}
65pub enum DeferredInput {
66    Key(Key),
67    GeneratedKey {
68        key: Key,
69        depth: usize,
70    },
71    Paste(String),
72    Leaf {
73        handler: fn(&mut Editor, char),
74        key: char,
75        remaining: usize,
76        depth: usize,
77    },
78    Macro {
79        keys: Arc<[Key]>,
80        position: usize,
81        repetitions: usize,
82        depth: usize,
83    },
84}
85pub struct ResolutionState {
86    pub tx: mpsc::Sender<ResolutionEvent>,
87    pub rx: Option<mpsc::Receiver<ResolutionEvent>>,
88    worker: Option<worker::ResolutionWorker>,
89    pending: Option<Pending>,
90    ready: Option<(ResolutionKey, Result<ResolutionData, String>)>,
91    pub queue: VecDeque<DeferredInput>,
92    pub staged: VecDeque<DeferredInput>,
93    pub in_action: bool,
94    resume_scheduled: bool,
95    started: bool,
96    stopping: bool,
97    pub enabled: bool,
98}
99impl Default for ResolutionState {
100    fn default() -> Self {
101        let (tx, rx) = mpsc::channel();
102        Self {
103            tx,
104            rx: Some(rx),
105            worker: None,
106            pending: None,
107            ready: None,
108            queue: VecDeque::new(),
109            staged: VecDeque::new(),
110            in_action: false,
111            resume_scheduled: false,
112            started: false,
113            stopping: false,
114            enabled: false,
115        }
116    }
117}
118impl ResolutionState {
119    pub fn blocked(&self) -> bool {
120        self.pending
121            .as_ref()
122            .is_some_and(|pending| pending.purpose.blocks_input())
123    }
124    pub fn pending(&self) -> bool {
125        self.pending.is_some() || self.stopping || !self.queue.is_empty()
126    }
127    pub fn cancel(&mut self) {
128        if let Some(pending) = self.pending.take() {
129            pending.cancel.store(true, Ordering::Release);
130        }
131    }
132    pub fn cancel_preview(&mut self) {
133        if self
134            .pending
135            .as_ref()
136            .is_some_and(|pending| !pending.purpose.blocks_input())
137        {
138            self.cancel();
139        }
140    }
141    pub fn stop(&mut self) {
142        self.cancel();
143        self.queue.clear();
144        self.staged.clear();
145        self.stopping = self.started;
146        self.worker = None;
147    }
148}
149
150impl Editor {
151    /// Small pure reads have a fixed source bound. Larger scans and counts never
152    /// run on input/render. This is a work bound, not a second grammar dialect.
153    pub(crate) fn resolution_is_large(&self, command: &Command) -> bool {
154        const INLINE_BYTES: usize = 4096;
155        self.resolution.enabled
156            && (self.buf().len_bytes() > INLINE_BYTES || command.count.unwrap_or(1) > INLINE_BYTES)
157    }
158    fn resolution_matches(
159        &self,
160        key: &ResolutionKey,
161        command: &Command,
162        cursors: &[usize],
163    ) -> bool {
164        !self.docs.is_empty()
165            && key.document == self.current()
166            && key.revision == self.buf().revision()
167            && key.pane == self.active_pane
168            && key.tab == self.cur_indent().width.max(1)
169            && &key.command == command
170            && key.cursors == cursors
171    }
172    fn prepared_resolution(
173        &self,
174        command: &Command,
175        cursors: &[usize],
176    ) -> Option<Result<&ResolutionData, &str>> {
177        let (key, ready) = self.resolution.ready.as_ref()?;
178        self.resolution_matches(key, command, cursors)
179            .then(|| ready.as_ref().map_err(String::as_str))
180    }
181    pub(crate) fn resolved_many(
182        &self,
183        command: &Command,
184        cursors: &[usize],
185    ) -> Result<Vec<Option<Resolved>>, String> {
186        if let Some(ready) = self.prepared_resolution(command, cursors) {
187            return ready
188                .map(|data| data.resolved.clone())
189                .map_err(str::to_owned);
190        }
191        strop_grammar::resolve_many(self.buf(), cursors, command).map_err(|error| error.to_string())
192    }
193    pub(crate) fn resolved_plan(
194        &self,
195        command: &Command,
196        cursors: &[usize],
197    ) -> Result<Option<ActionPlan>, String> {
198        if let Some(ready) = self.prepared_resolution(command, cursors) {
199            return ready.map(|data| data.plan.clone()).map_err(str::to_owned);
200        }
201        strop_grammar::plan(self.buf(), cursors, command).map_err(|error| error.to_string())
202    }
203    pub(crate) fn resolution_ready(&self, command: &Command, cursors: &[usize]) -> bool {
204        self.prepared_resolution(command, cursors).is_some()
205    }
206
207    pub(crate) fn defer_resolution(
208        &mut self,
209        command: &Command,
210        cursors: Vec<usize>,
211        purpose: ResolutionPurpose,
212    ) -> bool {
213        if !self.resolution_is_large(command) || self.resolution_ready(command, &cursors) {
214            return false;
215        }
216        if let Some(pending) = self.resolution.pending.as_ref() {
217            if self.resolution_matches(&pending.ticket.key, command, &cursors) {
218                if purpose.blocks_input() {
219                    if let Some(pending) = self.resolution.pending.as_mut() {
220                        pending.purpose = purpose;
221                    }
222                }
223                return true;
224            }
225        }
226        self.resolution.cancel();
227        if !self.resolution.started {
228            let started: Result<(), String> = match self.tape.call("grammar.start", &(), || {
229                worker::ResolutionWorker::start(self.resolution.tx.clone())
230                    .map(|worker| self.resolution.worker = Some(worker))
231                    .map_err(|error| error.to_string())
232            }) {
233                Ok(result) => result,
234                Err(error) => Err(error.to_string()),
235            };
236            if let Err(error) = started {
237                self.message = format!("grammar: {error}");
238                return true;
239            }
240            self.resolution.started = true;
241        }
242        let request = match self.worker_ids.allocate() {
243            Ok(request) => request,
244            Err(error) => {
245                self.message = error.message;
246                return true;
247            }
248        };
249        let key = ResolutionKey {
250            document: self.current(),
251            revision: self.buf().revision(),
252            pane: self.active_pane,
253            command: command.clone(),
254            cursors,
255            tab: self.cur_indent().width.max(1),
256        };
257        let ticket = Ticket { request, key };
258        let cancel = Arc::new(AtomicBool::new(false));
259        self.resolution.pending = Some(Pending {
260            ticket: ticket.clone(),
261            purpose,
262            cancel: cancel.clone(),
263            macro_depth: self.macro_depth,
264        });
265        match self.tape.request("grammar.resolve", &ticket) {
266            Ok(false) => return true,
267            Ok(true) => {}
268            Err(error) => {
269                self.handle_resolution(ResolutionEvent::Completed(Box::new(Completion {
270                    ticket,
271                    outcome: Outcome::failed(FailureKind::Protocol, error.to_string()),
272                })));
273                return true;
274            }
275        }
276        let work = worker::Work {
277            ticket,
278            rope: self.buf().snapshot(),
279            cancel,
280        };
281        let failed = match &self.resolution.worker {
282            Some(worker) => worker.resolve(work).err(),
283            None => Some(Box::new(work)),
284        };
285        if let Some(work) = failed {
286            self.handle_resolution(ResolutionEvent::Completed(Box::new(Completion {
287                ticket: work.ticket,
288                outcome: Outcome::failed(FailureKind::Disconnected, "grammar worker stopped"),
289            })));
290        }
291        true
292    }
293
294    pub(crate) fn handle_resolution(&mut self, event: ResolutionEvent) {
295        let ResolutionEvent::Completed(completion) = event else {
296            self.resolution.started = false;
297            self.resolution.stopping = false;
298            return;
299        };
300        if !self
301            .resolution
302            .pending
303            .as_ref()
304            .is_some_and(|pending| pending.ticket == completion.ticket)
305        {
306            return;
307        }
308        let Some(pending) = self.resolution.pending.take() else {
309            return;
310        };
311        let key = completion.ticket.key;
312        let current = !self.finishing
313            && !self.docs.is_empty()
314            && self.current() == key.document
315            && self.active_pane == key.pane
316            && self.buf().revision() == key.revision
317            && (!pending.purpose.blocks_input() || self.all_cursors() == key.cursors);
318        if !current {
319            self.resolution.queue.clear();
320            if !self.finishing {
321                self.message = "resolution cancelled: document or cursor changed".into();
322            }
323            return;
324        }
325        let ready = match completion.outcome {
326            Outcome::Success(data) => {
327                if !self.docs.get_mut(key.document).is_some_and(|document| {
328                    document
329                        .buf
330                        .install_line_layouts(key.revision, &data.layouts)
331                }) {
332                    self.message = "invalid layout publication".into();
333                    self.resolution.queue.clear();
334                    return;
335                }
336                Ok(data)
337            }
338            Outcome::Failed { failure, .. } => Err(failure.message),
339            Outcome::Cancelled(_) => {
340                self.resume_resolution_input();
341                return;
342            }
343        };
344        let succeeded = ready.is_ok();
345        if let Err(error) = &ready {
346            self.message = error.clone();
347        }
348        self.resolution.ready = Some((key.clone(), ready));
349        if succeeded {
350            let saved_depth = self.macro_depth;
351            self.macro_depth = pending.macro_depth;
352            self.run_input_action(|editor| match pending.purpose {
353                ResolutionPurpose::Motion => editor.move_cursor(&key.command),
354                ResolutionPurpose::Execute => editor.dispatch_grammar(&key.command),
355                ResolutionPurpose::IncSearch => editor.incsearch_jump(),
356                ResolutionPurpose::Preview => {}
357                ResolutionPurpose::VisualObject => editor.select_visual_object(&key.command),
358                ResolutionPurpose::RepeatSearch(invert) => editor.repeat_search(invert),
359                ResolutionPurpose::DotRepeat => editor.dot_repeat_pub(),
360            });
361            self.macro_depth = saved_depth;
362        }
363        self.resume_resolution_input();
364    }
365
366    pub(crate) fn resume_resolution_input(&mut self) {
367        self.resolution.resume_scheduled = false;
368        // Exactly one queued input per delivery keeps replay independent of
369        // host timing. The outer event loop owns the render/time budget.
370        if !self.resolution.blocked() && !self.should_quit {
371            if let Some(input) = self.resolution.queue.pop_front() {
372                self.run_input_action(|editor| {
373                    match input {
374                        DeferredInput::Key(key) => editor.feed_inner(key),
375                        DeferredInput::GeneratedKey { key, depth } => {
376                            let saved = editor.macro_depth;
377                            editor.macro_depth = depth;
378                            editor.feed_inner(key);
379                            editor.macro_depth = saved;
380                        }
381                        DeferredInput::Paste(text) => editor.paste_bracketed(&text),
382                        DeferredInput::Leaf {
383                            handler,
384                            key,
385                            remaining,
386                            depth,
387                        } => {
388                            let saved = editor.macro_depth;
389                            editor.macro_depth = depth;
390                            editor.run_counted_leaf(handler, key, remaining);
391                            editor.macro_depth = saved;
392                        }
393                        DeferredInput::Macro {
394                            keys,
395                            position,
396                            repetitions,
397                            depth,
398                        } => {
399                            editor.run_macro_step(keys, position, repetitions, depth);
400                        }
401                    }
402                    editor.prepare_resolution_preview();
403                });
404            }
405        }
406        self.schedule_resolution_input();
407    }
408}
409
410impl Editor {
411    pub(crate) fn run_input_action(&mut self, action: impl FnOnce(&mut Editor)) {
412        let nested = self.resolution.in_action;
413        self.resolution.in_action = true;
414        action(self);
415        self.resolution.in_action = nested;
416        if !nested {
417            while let Some(input) = self.resolution.staged.pop_back() {
418                self.resolution.queue.push_front(input);
419            }
420            self.schedule_resolution_input();
421            // Collections write back at normal-mode action boundaries
422            // (0044); the revision gate keeps this free for motions.
423            if self.mode == super::Mode::Normal {
424                self.maybe_sync_collection();
425            }
426        }
427    }
428    pub(crate) fn run_counted_leaf(
429        &mut self,
430        handler: fn(&mut Editor, char),
431        key: char,
432        count: usize,
433    ) {
434        if !self.resolution.enabled {
435            for _ in 0..count {
436                handler(self, key);
437            }
438            return;
439        }
440        if count == 0 {
441            return;
442        }
443        handler(self, key);
444        if count > 1 {
445            self.resolution.queue.push_front(DeferredInput::Leaf {
446                handler,
447                key,
448                remaining: count - 1,
449                depth: self.macro_depth,
450            });
451        }
452        self.schedule_resolution_input();
453    }
454    fn schedule_resolution_input(&mut self) {
455        if !self.resolution.blocked()
456            && !self.resolution.queue.is_empty()
457            && !self.resolution.resume_scheduled
458        {
459            self.resolution.resume_scheduled = true;
460            if let Some(sender) = &self.app_tx {
461                if sender.send(super::events::AppEvent::ResumeInput).is_err() {
462                    self.message = "deferred input channel closed".into();
463                }
464            }
465        }
466    }
467    pub(crate) fn queue_macro(&mut self, keys: Vec<Key>, repetitions: usize, depth: usize) {
468        if keys.is_empty() || repetitions == 0 {
469            return;
470        }
471        self.resolution.queue.push_front(DeferredInput::Macro {
472            keys: keys.into(),
473            position: 0,
474            repetitions,
475            depth,
476        });
477        self.schedule_resolution_input();
478    }
479    fn run_macro_step(
480        &mut self,
481        keys: Arc<[Key]>,
482        position: usize,
483        repetitions: usize,
484        depth: usize,
485    ) {
486        let key = keys[position];
487        let next = position + 1;
488        if next < keys.len() {
489            self.resolution.queue.push_front(DeferredInput::Macro {
490                keys,
491                position: next,
492                repetitions,
493                depth,
494            });
495        } else if repetitions > 1 {
496            self.resolution.queue.push_front(DeferredInput::Macro {
497                keys,
498                position: 0,
499                repetitions: repetitions - 1,
500                depth,
501            });
502        }
503        let saved = self.macro_depth;
504        self.macro_depth = depth;
505        self.feed_inner(key);
506        self.macro_depth = saved;
507    }
508}