1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#[macro_use]
extern crate quick_error;

use std::sync::Arc;
use std::borrow::Cow;
use std::path::Path;

#[cfg(target_os = "macos")]
mod macos;

#[cfg(target_os = "macos")]
use macos as system_impl;

#[cfg(not(target_os = "macos"))]
mod xdg;

#[cfg(not(target_os = "macos"))]
use xdg as system_impl;

/// Abstract representation of a code editor.
///
/// It's cheap to clone.
#[derive(Clone)]
pub struct Editor {
    inner: Arc<dyn EditorImpl>
}

impl Editor {
    /// Create a new instance appropriate for opening files of this type
    ///
    /// ```
    /// # use open_in_editor::Editor;
    /// Editor::new_for_file_extension("rs");
    /// ```
    #[inline]
    pub fn new_for_file_extension(ext: &str) -> Option<Self> {
        system_impl::editor_for_file_extension(ext)
    }

    /// Open one or more files in this editor.
    ///
    /// Paths can be directories, but it's not guaranteed
    /// that all editors will open directories in a sensible way.
    ///
    /// This call is non-blocking.
    #[inline]
    pub fn open_paths<Pathlike: AsRef<Path>>(&self, paths: impl IntoIterator<Item=Pathlike>) -> Result<(), Error> {
        let paths: Vec<_> = paths.into_iter().collect();
        let paths: Vec<_> = paths.iter().map(|p| p.as_ref()).collect();
        self.inner.open_paths(paths.as_slice())
    }

    /// Human-friendly brand name of the editor
    #[inline]
    pub fn name(&self) -> Cow<str> {
        self.inner.name()
    }
}

quick_error! {
    /// Failure reasons
    #[derive(Debug)]
    pub enum Error {
        /// one or more paths could not be used
        InvalidPaths {
            display("one or more paths could not be used")
        }

        /// the editor is gone or unusable, may have been deleted or incorrectly detected
        EditorInvalidated {
            display("the editor is gone or unusable, may have been deleted or incorrectly detected")
        }

        /// execution failed
        Status(n: i32) {
            display("execution failed, status code: {}", n)
        }

        /// workspace unavailable, can't connect to the current desktop session
        WorkspaceUnavailable {
            display("workspace unavailable, can't connect to the current desktop session")
        }

        /// I/O error (path may not exist, or permission error, etc.)
        IO(k: std::io::ErrorKind) {
            display("I/O error: {:?}", k)
        }
    }
}

impl From<std::io::Error> for Error {
    fn from(e: std::io::Error) -> Self {
        Self::IO(e.kind())
    }
}

pub use system_impl::reveal_paths;

/// A handle for a code editor
pub(crate) trait EditorImpl: Send + Sync {
    fn open_paths<'a>(&self, paths: &[&Path]) -> Result<(), Error>;
    fn name(&self) -> Cow<str>;
}

#[test]
fn spawn_rs() {
    let editor = Editor::new_for_file_extension("rs").unwrap();
    editor.open_paths(&["src"]).unwrap();
}

#[test]
fn reveal() {
    reveal_paths(&[Path::new("src/macos")]).unwrap();
}