idet-core 0.3.0

Shared text-editing library for a Micro-like terminal editor and a gedit-like egui GUI
Documentation
//! File dialogs and their document-state plumbing, shared by both frontends.

use document_state::{Document, FileFormat, SaveError};
use std::fmt::Debug;
use std::path::Path;

/// What a save attempt did.
pub enum SaveOutcome {
    /// The document was written to disk.
    Saved,
    /// The user dismissed the dialog asking for a path.
    Cancelled,
    /// Writing failed, with a message ready to show.
    Failed(String),
}

/// What an open attempt did.
pub enum OpenOutcome<T: FileFormat + Clone> {
    /// The file was read and parsed.
    Opened(Box<Document<T>>),
    /// The user dismissed the dialog.
    Cancelled,
    /// Reading or parsing failed, with a message ready to show.
    Failed(String),
}

/// Saves `document`, asking for a path through a dialog when it has none.
pub fn save<T: FileFormat + Clone>(document: &mut Document<T>) -> SaveOutcome {
    match document.save() {
        Ok(()) => SaveOutcome::Saved,
        Err(SaveError::NoPath) => save_as(document),
        Err(SaveError::Write(error)) => SaveOutcome::Failed(format!("failed to save: {error}")),
    }
}

/// Asks for a path through a dialog and saves `document` there, adopting it.
pub fn save_as<T: FileFormat + Clone>(document: &mut Document<T>) -> SaveOutcome {
    let Some(path) = rfd::FileDialog::new()
        .set_directory(document.dialog_directory())
        .save_file()
    else {
        return SaveOutcome::Cancelled;
    };
    match document.save_as(path) {
        Ok(()) => SaveOutcome::Saved,
        Err(error) => SaveOutcome::Failed(format!("failed to save: {error}")),
    }
}

/// Asks for a file through a dialog and opens it as a new document.
///
/// `current` only supplies the directory the dialog starts in.
pub fn open<T: FileFormat + Clone>(current: &Document<T>) -> OpenOutcome<T>
where
    T::Error: Debug,
{
    let Some(path) = rfd::FileDialog::new()
        .set_directory(current.dialog_directory())
        .pick_file()
    else {
        return OpenOutcome::Cancelled;
    };
    open_path(&path)
}

/// Opens `path` as a new document, without any dialog.
#[must_use]
pub fn open_path<T: FileFormat + Clone>(path: &Path) -> OpenOutcome<T>
where
    T::Error: Debug,
{
    match Document::<T>::open(path) {
        Ok(document) => OpenOutcome::Opened(Box::new(document)),
        Err(error) => OpenOutcome::Failed(format!("failed to open {}: {error}", path.display())),
    }
}