summaryrefslogtreecommitdiffstats
path: root/src/tab.rs
blob: bf7c2ffafa73e005c46734e88cdf42178db7be55 (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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
use std::path;
use std::rc::Rc;

use users::UsersCache;

use crate::args::Args;
use crate::completion::Completion;
use crate::config::Colors;
use crate::content_window::ContentWindow;
use crate::fileinfo::{FileInfo, FileKind, PathContent};
use crate::filter::FilterKind;
use crate::fm_error::{FmError, FmResult};
use crate::input::Input;
use crate::mode::Mode;
use crate::preview::{Directory, Preview};
use crate::selectable_content::SelectableContent;
use crate::shortcut::Shortcut;
use crate::visited::History;

/// Holds every thing about the current tab of the application.
/// Most of the mutation is done externally.
#[derive(Clone)]
pub struct Tab {
    /// The mode the application is currenty in
    pub mode: Mode,
    /// The mode previously set
    pub previous_mode: Mode,
    /// The indexes of displayed file
    pub window: ContentWindow,
    /// Files marked as flagged
    pub input: Input,
    /// Files in current path
    pub path_content: PathContent,
    /// Height of the terminal window
    pub height: usize,
    /// read from command line
    pub show_hidden: bool,
    /// NVIM RPC server address
    pub nvim_server: String,
    /// Completion list and index in it.
    pub completion: Completion,
    /// True if the user issued a quit event (`Key::Char('q')` by default).
    /// It's used to exit the main loop before reseting the cursor.
    pub must_quit: bool,
    /// Lines of the previewed files.
    /// Empty if not in preview mode.
    pub preview: Preview,
    /// Visited directories
    pub history: History,
    /// Predefined shortcuts
    pub shortcut: Shortcut,
    /// Last searched string
    pub searched: Option<String>,
    /// Optional tree view
    pub directory: Directory,
    /// The filter use before displaying files
    pub filter: FilterKind,
}

impl Tab {
    /// Creates a new tab from args and height.
    pub fn new(args: Args, height: usize, users_cache: Rc<UsersCache>) -> FmResult<Self> {
        let path = std::fs::canonicalize(path::Path::new(&args.path))?;
        let directory = Directory::empty(&path, &users_cache)?;
        let filter = FilterKind::All;
        let show_hidden = false;
        let path_content = PathContent::new(&path, users_cache, &filter, show_hidden)?;
        let show_hidden = false;
        let nvim_server = args.server;
        let mode = Mode::Normal;
        let previous_mode = Mode::Normal;
        let window = ContentWindow::new(path_content.content.len(), height);
        let input = Input::default();
        let completion = Completion::default();
        let must_quit = false;
        let preview = Preview::Empty;
        let mut history = History::default();
        history.push(&path);
        let shortcut = Shortcut::new();
        let searched = None;
        Ok(Self {
            mode,
            previous_mode,
            window,
            input,
            path_content,
            height,
            nvim_server,
            completion,
            must_quit,
            preview,
            history,
            shortcut,
            searched,
            directory,
            filter,
            show_hidden,
        })
    }

    /// Fill the input string with the currently selected completion.
    pub fn fill_completion(&mut self) -> FmResult<()> {
        self.completion.set_kind(&self.mode);
        let current_path = self.path_content_str().unwrap_or_default().to_owned();
        self.completion
            .complete(&self.input.string(), &self.path_content, &current_path)
    }

    /// Refresh the current view.
    /// Input string is emptied, the files are read again, the window of
    /// displayed files is reset.
    /// The first file is selected.
    pub fn refresh_view(&mut self) -> FmResult<()> {
        self.filter = FilterKind::All;
        self.input.reset();
        self.path_content
            .reset_files(&self.filter, self.show_hidden)?;
        self.window.reset(self.path_content.content.len());
        self.preview = Preview::new_empty();
        self.completion.reset();
        Ok(())
    }

    /// Move to the currently selected directory.
    /// Fail silently if the current directory is empty or if the selected
    /// file isn't a directory.
    pub fn go_to_child(&mut self) -> FmResult<()> {
        let childpath = &self
            .path_content
            .selected()
            .ok_or_else(|| FmError::custom("go_to_child", "Empty directory"))?
            .path
            .clone();
        self.history.push(childpath);
        self.set_pathcontent(childpath)?;
        self.window.reset(self.path_content.content.len());
        self.input.cursor_start();
        Ok(())
    }

    /// Set the height of the window and itself.
    pub fn set_height(&mut self, height: usize) {
        self.window.set_height(height);
        self.height = height;
    }

    /// Returns `true` iff the application has to quit.
    /// This methods allows use to reset the cursors and other
    /// terminal parameters gracefully.
    pub fn must_quit(&self) -> bool {
        self.must_quit
    }

    /// Returns a string of the current directory path.
    pub fn path_content_str(&self) -> Option<&str> {
        self.path_content.path.to_str()
    }

    /// Set the pathcontent to a new path.
    /// Reset the window.
    /// Add the last path to the history of visited paths.
    pub fn set_pathcontent(&mut self, path: &path::Path) -> FmResult<()> {
        self.history.push(path);
        self.path_content
            .change_directory(path, &self.filter, self.show_hidden)?;
        self.window.reset(self.path_content.content.len());
        Ok(())
    }

    /// Set the window. Doesn't require the lenght to be known.
    pub fn set_window(&mut self) {
        let len = self.path_content.content.len();
        self.window.reset(len);
    }
    /// Apply the filter.
    pub fn set_filter(&mut self, filter: FilterKind) {
        self.filter = filter
    }

    /// Set the line index to `index` and scroll there.
    pub fn scroll_to(&mut self, index: usize) {
        self.window.scroll_to(index);
    }

    /// Returns the correct index jump target to a flagged files.
    pub fn find_jump_index(&self, jump_target: &path::Path) -> Option<usize> {
        self.path_content
            .content
            .iter()
            .position(|file| file.path == jump_target)
    }

    /// Refresh the shortcuts. It drops non "hardcoded" shortcuts and
    /// extend the vector with the mount points.
    pub fn refresh_shortcuts(&mut self, mount_points: &[&path::Path]) {
        self.shortcut.refresh(mount_points)
    }

    /// Select the file at index and move the window to this file.
    pub fn go_to_index(&mut self, index: usize) {
        self.path_content.select_index(index);
        self.window.scroll_to(index);
    }

    /// Refresh the existing users.
    pub fn refresh_users(&mut self, users_cache: Rc<UsersCache>) -> FmResult<()> {
        self.path_content
            .refresh_users(users_cache, &self.filter, self.show_hidden)
    }

    /// Search in current directory for an file whose name contains `searched_name`,
    /// from a starting position `next_index`.
    /// We search forward from that position and start again from top if nothing is found.
    /// We move the selection to the first matching file.
    pub fn search_from(&mut self, searched_name: &str, current_index: usize) {
        let mut found = false;
        let mut next_index = current_index;
        // search after current position
        for (index, file) in self.path_content.enumerate().skip(current_index) {
            if file.filename.contains(searched_name) {
                next_index = index;
                found =