strop-engine 0.32.4

strop editor engine: documents, grammar, services and sessions — no frontend UI dependencies
use super::*;

impl Editor {
    pub(crate) fn prepare_filesystem(
        &mut self,
        intents: Vec<OperationIntent>,
        recovery: Option<recovery::RecoveryGuard>,
    ) -> Result<(), String> {
        if self.filesystem.pending() {
            return Err("another filesystem request is still running".into());
        }
        if intents.is_empty() {
            return Err("no filesystem entries selected".into());
        }
        if intents.len() > strop_fs::batch::STEP_LIMIT {
            return Err("filesystem review is limited to 512 steps".into());
        }
        if recovery.is_some() && intents.len() != 1 {
            return Err("checked recovery names exactly one receipt step".into());
        }
        self.filesystem_admission(&intents, None)?;
        if self.filesystem.pending.is_some() {
            return Err("apply or cancel the current filesystem review first".into());
        }
        let mut copies = HashMap::new();
        for intent in &intents {
            if intent.kind == OperationKind::Copy && intent.copy_version == CopyVersion::Buffer {
                let location = intent
                    .source
                    .as_ref()
                    .ok_or("buffer copy requires a source")?;
                let target = crate::files::FileTarget::from_location(location)
                    .map_err(|error| error.to_string())?;
                let source = self
                    .docs
                    .iter()
                    .find_map(|(_, document)| document.matches_target(&target).then_some(document))
                    .ok_or("copy current buffer requires an open source document")?;
                copies.insert(location.clone(), source.buf.snapshot());
            }
        }
        let intents = std::sync::Arc::new(intents);
        let key = FsKey {
            origin: self.current(),
            revision: self.buf().revision(),
            focus: self.focus_epoch,
            open_created: intents.len() == 1
                && matches!(
                    intents[0].kind,
                    OperationKind::CreateFile | OperationKind::CreateDirectory
                ),
            intents: intents.clone(),
            recovery: recovery.clone(),
            draft: None,
        };
        let environment = self.filesystem.environment.clone();
        self.start_filesystem_preparation(key, copies, move |token| match strop_fs::batch::prepare(
            &intents,
            &environment,
            &token,
        ) {
            Ok(batch)
                if recovery
                    .as_ref()
                    .is_some_and(|guard| batch.refused.is_empty() && !guard.accepts(&batch)) =>
            {
                Outcome::failed(
                    FailureKind::InvalidInput,
                    "recovery object changed since its receipt; no mutation admitted",
                )
            }
            Ok(batch) => Outcome::Success(PreparedFilesystem {
                batch,
                draft_targets: Vec::new(),
            }),
            Err(error) => Outcome::failed(FailureKind::Io, error.to_string()),
        })
    }

    pub(super) fn start_filesystem_preparation(
        &mut self,
        key: FsKey,
        copies: HashMap<ResourceLocation, ropey::Rope>,
        work: impl FnOnce(worker::CancelToken) -> Outcome<PreparedFilesystem> + Send + 'static,
    ) -> Result<(), String> {
        if self.filesystem.pending() || self.filesystem.pending.is_some() {
            return Err("finish the current filesystem request or review first".into());
        }
        let request = self.worker_ids.allocate().map_err(|error| error.message)?;
        let ticket = Ticket { request, key };
        self.filesystem.preparing = Some(ticket.clone());
        self.filesystem.preparing_copies = copies;
        self.message = "preparing filesystem review; nothing applied".into();
        let launch = self.tape.request("filesystem.prepare", &ticket);
        match launch {
            Ok(false) => return Ok(()),
            Ok(true) => {}
            Err(error) => {
                self.handle_filesystem(FsEvent::Prepared(Box::new(Completion {
                    ticket,
                    outcome: Outcome::failed(FailureKind::Protocol, error.to_string()),
                })));
                return Ok(());
            }
        }
        let tx = self.io.tx.clone();
        let handle = worker::spawn(
            "strop-fs-prepare",
            move |outcome| {
                let _ = tx.send(IoEvent::Filesystem(Box::new(FsEvent::Prepared(Box::new(
                    Completion { ticket, outcome },
                )))));
            },
            work,
        );
        self.worker_handles.insert(request, handle);
        Ok(())
    }
}