Skip to main content

strop_engine/editor/normal/
preview.rs

1//! normal/preview.rs — the pending-operation preview (0001 pillar 2).
2//! The same typed command and saved origin feed preview and execution:
3//! a search prompt composes with its operator exactly as typed.
4
5use strop_core::Range;
6use strop_grammar::{self as grammar, Parse};
7
8use crate::editor::Editor;
9
10impl Editor {
11    /// The live preview: what would the pending keys do right now?
12    /// The plan the executor would apply — every cursor's range (0014
13    /// wave 3): the preview cannot lie. Query errors surface as `Err`
14    /// (the modeline shows them); no operator or no origin means no
15    /// preview at all.
16    pub fn preview(&self) -> Result<Option<(Vec<Range>, String)>, String> {
17        let Some((command, cursors)) = self.preview_command()? else {
18            return Ok(None);
19        };
20        if command.op.is_none()
21            || (self.resolution_is_large(&command) && !self.resolution_ready(&command, &cursors))
22        {
23            return Ok(None);
24        }
25        let resolved = self.resolved_many(&command, &cursors)?;
26        let Some(Some(primary)) = resolved.first() else {
27            return Ok(None);
28        };
29        let Some(plan) = self.resolved_plan(&command, &cursors)? else {
30            return Ok(None);
31        };
32        Ok(Some((
33            plan.targets.iter().map(|target| target.range).collect(),
34            primary.spec.clone(),
35        )))
36    }
37
38    pub(crate) fn prepare_resolution_preview(&mut self) {
39        if self.resolution.blocked()
40            || self
41                .pending
42                .prompt()
43                .is_some_and(|prompt| prompt.search().is_some())
44        {
45            return;
46        }
47        match self.preview_command() {
48            Ok(Some((command, cursors))) => {
49                self.defer_resolution(
50                    &command,
51                    cursors,
52                    super::super::resolution::ResolutionPurpose::Preview,
53                );
54            }
55            Ok(None) => self.resolution.cancel_preview(),
56            Err(error) => {
57                self.resolution.cancel_preview();
58                self.message = error;
59            }
60        }
61    }
62
63    fn preview_command(&self) -> Result<Option<(grammar::Command, Vec<usize>)>, String> {
64        if let Some(prompt) = self.pending.prompt() {
65            let Some((origin, state)) = prompt.search() else {
66                return Ok(None);
67            };
68            if state.op.is_none() || !self.pending_origin_valid(origin) {
69                return Ok(None);
70            }
71            let Some(command) = self.search_prompt_command(prompt, false)? else {
72                return Ok(None);
73            };
74            Ok(Some((command, origin.pane.sels.heads())))
75        } else {
76            let Some((operator, motion)) = self.walker.op_motion() else {
77                return Ok(None);
78            };
79            let keys = operator.key().to_string() + motion;
80            let mut command = match grammar::parse(&keys) {
81                Parse::Complete(command) => command,
82                Parse::QueryError(error) => return Err(error.to_string()),
83                Parse::Incomplete | Parse::Invalid => return Ok(None),
84            };
85            command.count = self.walker.state.count();
86            command.register = self.walker.state.register;
87            Ok(Some((command, self.all_cursors())))
88        }
89    }
90}