summaryrefslogtreecommitdiffstats
path: root/src/app/selection.rs
blob: f435dcb7a56d7fc47bf36e0ffad45c004290c009 (plain)
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use {
    super::{
        AppContext,
        CmdResult,
    },
    crate::{
        errors::ProgramError,
        launchable::Launchable,
        stage::Stage,
    },
    std::{
        fs::OpenOptions,
        io::Write,
        path::Path,
    },
};

/// the id of a line, starting at 1
/// (0 if not specified)
pub type LineNumber = usize;

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum SelectionType {
    File,
    Directory,
    Any,
}

/// light information about the currently selected
/// file and maybe line number
#[derive(Debug, Clone, Copy)]
pub struct Selection<'s> {
    pub path: &'s Path,
    pub line: LineNumber, // the line number in the file (0 if none selected)
    pub stype: SelectionType,
    pub is_exe: bool,
}

#[derive(Debug)]
pub enum SelInfo<'s> {
    None,
    One(Selection<'s>),
    More(&'s Stage), // by contract the stage contains at least 2 paths
}

impl SelectionType {
    pub fn respects(self, constraint: Self) -> bool {
        constraint == Self::Any || self == constraint
    }
    pub fn is_respected_by(self, sel_type: Option<Self>) -> bool {
        match (self, sel_type) {
            (Self::File, Some(Self::File)) => true,
            (Self::Directory, Some(Self::Directory)) => true,
            (Self::Any, _) => true,
            _ => false,
        }
    }
    pub fn from(path: &Path) -> Self {
        if path.is_dir() {
            Self::Directory
        } else {
            Self::File
        }
    }
}


impl Selection<'_> {

    /// build a CmdResult with a launchable which will be used to
    ///  1/ quit broot
    ///  2/ open the relevant file the best possible way
    pub fn to_opener(
        self,
        con: &AppContext,
    ) -> Result<CmdResult, ProgramError> {
        Ok(if self.is_exe {
            let path = self.path.to_string_lossy().to_string();
            if let Some(export_path) = &con.launch_args.cmd_export_path {
                // broot was launched as br, we can launch the executable from the shell
                let f = OpenOptions::new().append(true).open(export_path)?;
                writeln!(&f, "{}", path)?;
                CmdResult::Quit
            } else {
                CmdResult::from(Launchable::program(
                    vec![path],
                    None, // we don't set the working directory
                    con,
                )?)
            }
        } else {
            CmdResult::from(Launchable::opener(self.path.to_path_buf()))
        })
    }
}

impl<'a> SelInfo<'a> {
    pub fn count_paths(&self) -> usize {
        match self {
            SelInfo::None => 0,
            SelInfo::One(_) => 1,
            SelInfo::More(stage) => stage.len(),
        }
    }
    pub fn common_stype(&self) -> Option<SelectionType> {
        match self {
            SelInfo::None => None,
            SelInfo::One(sel) => Some(sel.stype),
            SelInfo::More(stage) => {
                let stype = SelectionType::from(&stage.paths()[0]);
                for path in stage.paths().iter().skip(1) {
                    if stype != SelectionType::from(path) {
                        return None;
                    }
                }
                Some(stype)
            }
        }
    }
    pub fn one_sel(self) -> Option<Selection<'a>> {
        match self {
            SelInfo::One(sel) => Some(sel),
            _ => None,
        }
    }
    pub fn extension(&self) -> Option<&str> {
        match self {
            SelInfo::None => None,
            SelInfo::One(sel) => sel.path.extension().and_then(|e| e.to_str()),
            SelInfo::More(stage) => {
                let common_extension = stage.paths()[0]
                    .extension().and_then(|e| e.to_str());
                if common_extension.is_none() {
                    return None;
                }
                for path in stage.paths().iter().skip(1) {
                    let extension = path.extension().and_then(|e| e.to_str());
                    if extension != common_extension {
                        return None;
                    }
                }
                common_extension
            }
        }
    }
}