cursive_open_editor/
strategy.rs

1use std::{
2  io::{self},
3  path::PathBuf,
4};
5
6use tempfile::TempPath;
7
8/// How to select the editor.
9#[derive(Debug, Clone, Default)]
10pub enum FindEditorStrategy {
11  /// Checks, in order, the `CURSIVE_EDITOR`, `EDITOR`, and `VISUAL` environment variables.
12  /// Executes the first one that is set.
13  #[default]
14  Envs,
15  /// Execute the editor binary at the given path.
16  AbsolutePath(PathBuf),
17}
18
19impl FindEditorStrategy {
20  /// Helper constructor for `AbsolutePath`.
21  pub fn absolute_path<P: Into<PathBuf>>(p: P) -> Self {
22    Self::AbsolutePath(p.into())
23  }
24
25  /// Turn it into a real path.
26  pub fn editor_path(self) -> Option<PathBuf> {
27    match self {
28      FindEditorStrategy::Envs => ["CURSIVE_EDITOR", "EDITOR", "VISUAL"]
29        .into_iter()
30        .find_map(|var_name| std::env::var_os(var_name))
31        .map(|os_str| os_str.into()),
32      FindEditorStrategy::AbsolutePath(it) => Some(it.to_owned()),
33    }
34  }
35}
36
37/// How to select which file to open.
38#[derive(Debug, Clone, Default)]
39pub enum EditPathStrategy {
40  /// Make a temporary file and open that.
41  #[default]
42  MakeTmp,
43  /// Open the file at the given path.
44  GivePath(PathBuf),
45}
46
47impl EditPathStrategy {
48  /// Return a path to edit a file at.
49  ///
50  /// `MakeTmp` uses the `tempfile` crate, which uses RAII handles for
51  /// its temporary files.
52  /// All this is hidden inside the [`EditPathStrategyTmpRAII`].
53  /// Keep it until you are through editing, then drop it.
54  pub fn file_path(self) -> io::Result<EditPathStrategyOut> {
55    Ok(EditPathStrategyOut(match self {
56      EditPathStrategy::MakeTmp => {
57        let tmp = tempfile::NamedTempFile::new()?;
58        EditPathStrategyOutInner::MadeTmp(tmp.into_temp_path())
59      }
60      EditPathStrategy::GivePath(path) => {
61        EditPathStrategyOutInner::GivenPath(path.to_owned())
62      }
63    }))
64  }
65}
66
67/// The path an [`EditPathStrategy`] settled on.
68///
69/// This may contain a RAII guard for a temporary file!
70/// Dropping it before you read from the file
71/// (for example, if you save the path but not the file)
72/// may invalidate the file.
73pub struct EditPathStrategyOut(EditPathStrategyOutInner);
74
75pub(crate) enum EditPathStrategyOutInner {
76  GivenPath(PathBuf),
77  MadeTmp(TempPath),
78}
79
80impl EditPathStrategyOut {
81  /// Get the path edited.
82  pub fn path(&self) -> PathBuf {
83    match &self.0 {
84      EditPathStrategyOutInner::GivenPath(it) => it.to_owned(),
85      EditPathStrategyOutInner::MadeTmp(tmp_path) => tmp_path.to_path_buf(),
86    }
87  }
88
89  /// If this was previously a temporary file, make the file persistent.
90  /// This removes any RAII this may have; you'll have to clean up the
91  /// file yourself.
92  pub fn persist(&mut self) -> io::Result<()> {
93    let EditPathStrategyOutInner::MadeTmp(_) = &self.0 else {
94      // nothing to do
95      return Ok(());
96    };
97    // force-get
98    let EditPathStrategyOutInner::MadeTmp(it) = std::mem::replace(
99      &mut self.0,
100      EditPathStrategyOutInner::GivenPath("slhgdsf".into()),
101    ) else {
102      unreachable!()
103    };
104    match it.keep() {
105      Ok(path) => {
106        self.0 = EditPathStrategyOutInner::GivenPath(path);
107        Ok(())
108      }
109      Err(ono) => {
110        // put it back!
111        self.0 = EditPathStrategyOutInner::MadeTmp(ono.path);
112        Err(ono.error)
113      }
114    }
115  }
116}