@@ -31,6 +31,242 @@ use helix_view::{
31
31
Document , Editor ,
32
32
} ;
33
33
34
+ // based on exa but not sure where to put this
35
+ /// More readable aliases for the permission bits exposed by libc.
36
+ #[ allow( trivial_numeric_casts) ]
37
+ mod modes {
38
+ // The `libc::mode_t` type’s actual type varies, but the value returned
39
+ // from `metadata.permissions().mode()` is always `u32`.
40
+ pub type Mode = u32 ;
41
+
42
+ pub const USER_READ : Mode = libc:: S_IRUSR as Mode ;
43
+ pub const USER_WRITE : Mode = libc:: S_IWUSR as Mode ;
44
+ pub const USER_EXECUTE : Mode = libc:: S_IXUSR as Mode ;
45
+
46
+ pub const GROUP_READ : Mode = libc:: S_IRGRP as Mode ;
47
+ pub const GROUP_WRITE : Mode = libc:: S_IWGRP as Mode ;
48
+ pub const GROUP_EXECUTE : Mode = libc:: S_IXGRP as Mode ;
49
+
50
+ pub const OTHER_READ : Mode = libc:: S_IROTH as Mode ;
51
+ pub const OTHER_WRITE : Mode = libc:: S_IWOTH as Mode ;
52
+ pub const OTHER_EXECUTE : Mode = libc:: S_IXOTH as Mode ;
53
+
54
+ pub const STICKY : Mode = libc:: S_ISVTX as Mode ;
55
+ pub const SETGID : Mode = libc:: S_ISGID as Mode ;
56
+ pub const SETUID : Mode = libc:: S_ISUID as Mode ;
57
+ }
58
+
59
+ // based on exa but not sure where to put this
60
+ mod fields {
61
+ use super :: modes;
62
+ use std:: fmt;
63
+ use std:: fs:: Metadata ;
64
+ use std:: os:: unix:: fs:: { FileTypeExt , MetadataExt } ;
65
+
66
+ /// The file’s base type, which gets displayed in the very first column of the
67
+ /// details output.
68
+ ///
69
+ /// This type is set entirely by the filesystem, rather than relying on a
70
+ /// file’s contents. So “link” is a type, but “image” is just a type of
71
+ /// regular file. (See the `filetype` module for those checks.)
72
+ ///
73
+ /// Its ordering is used when sorting by type.
74
+ #[ derive( PartialEq , Eq , PartialOrd , Ord , Copy , Clone ) ]
75
+ pub enum Type {
76
+ Directory ,
77
+ File ,
78
+ Link ,
79
+ Pipe ,
80
+ Socket ,
81
+ CharDevice ,
82
+ BlockDevice ,
83
+ Special ,
84
+ }
85
+
86
+ pub fn filetype ( metadata : & Metadata ) -> Type {
87
+ let filetype = metadata. file_type ( ) ;
88
+ if metadata. is_file ( ) {
89
+ Type :: File
90
+ } else if metadata. is_dir ( ) {
91
+ Type :: Directory
92
+ } else if filetype. is_fifo ( ) {
93
+ Type :: Pipe
94
+ } else if filetype. is_symlink ( ) {
95
+ Type :: Link
96
+ } else if filetype. is_char_device ( ) {
97
+ Type :: CharDevice
98
+ } else if filetype. is_block_device ( ) {
99
+ Type :: BlockDevice
100
+ } else if filetype. is_socket ( ) {
101
+ Type :: Socket
102
+ } else {
103
+ Type :: Special
104
+ }
105
+ }
106
+
107
+ impl fmt:: Display for Type {
108
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
109
+ write ! (
110
+ f,
111
+ "{}" ,
112
+ match self {
113
+ Type :: Directory => 'd' ,
114
+ Type :: File => '.' ,
115
+ Type :: Link => 'l' ,
116
+ Type :: Pipe => '|' ,
117
+ Type :: Socket => 's' ,
118
+ Type :: CharDevice => 'c' ,
119
+ Type :: BlockDevice => 'b' ,
120
+ Type :: Special => '?' ,
121
+ }
122
+ )
123
+ }
124
+ }
125
+
126
+ /// The file’s Unix permission bitfield, with one entry per bit.
127
+ #[ derive( Copy , Clone ) ]
128
+ pub struct Permissions {
129
+ pub user_read : bool ,
130
+ pub user_write : bool ,
131
+ pub user_execute : bool ,
132
+
133
+ pub group_read : bool ,
134
+ pub group_write : bool ,
135
+ pub group_execute : bool ,
136
+
137
+ pub other_read : bool ,
138
+ pub other_write : bool ,
139
+ pub other_execute : bool ,
140
+
141
+ pub sticky : bool ,
142
+ pub setgid : bool ,
143
+ pub setuid : bool ,
144
+ }
145
+
146
+ pub fn permissions ( metadata : & Metadata ) -> Permissions {
147
+ let bits = metadata. mode ( ) ;
148
+ let has_bit = |bit| bits & bit == bit;
149
+ Permissions {
150
+ user_read : has_bit ( modes:: USER_READ ) ,
151
+ user_write : has_bit ( modes:: USER_WRITE ) ,
152
+ user_execute : has_bit ( modes:: USER_EXECUTE ) ,
153
+
154
+ group_read : has_bit ( modes:: GROUP_READ ) ,
155
+ group_write : has_bit ( modes:: GROUP_WRITE ) ,
156
+ group_execute : has_bit ( modes:: GROUP_EXECUTE ) ,
157
+
158
+ other_read : has_bit ( modes:: OTHER_READ ) ,
159
+ other_write : has_bit ( modes:: OTHER_WRITE ) ,
160
+ other_execute : has_bit ( modes:: OTHER_EXECUTE ) ,
161
+
162
+ sticky : has_bit ( modes:: STICKY ) ,
163
+ setgid : has_bit ( modes:: SETGID ) ,
164
+ setuid : has_bit ( modes:: SETUID ) ,
165
+ }
166
+ }
167
+
168
+ impl fmt:: Display for Permissions {
169
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
170
+ let bit = |bit, char| if bit { char } else { "-" } ;
171
+ write ! (
172
+ f,
173
+ "{}{}{}{}{}{}{}{}{}" ,
174
+ bit( self . user_read, "r" ) ,
175
+ bit( self . user_write, "w" ) ,
176
+ bit( self . user_execute, "x" ) ,
177
+ bit( self . group_read, "r" ) ,
178
+ bit( self . group_write, "w" ) ,
179
+ bit( self . group_execute, "x" ) ,
180
+ bit( self . other_read, "r" ) ,
181
+ bit( self . other_write, "w" ) ,
182
+ bit( self . other_execute, "x" ) ,
183
+ )
184
+ }
185
+ }
186
+
187
+ /// A file’s size, in bytes. This is usually formatted by the `number_prefix`
188
+ /// crate into something human-readable.
189
+ #[ derive( Copy , Clone ) ]
190
+ pub enum Size {
191
+ /// This file has a defined size.
192
+ Some ( u64 ) ,
193
+
194
+ /// This file has no size, or has a size but we aren’t interested in it.
195
+ ///
196
+ /// Under Unix, directory entries that aren’t regular files will still
197
+ /// have a file size. For example, a directory will just contain a list of
198
+ /// its files as its “contents” and will be specially flagged as being a
199
+ /// directory, rather than a file. However, seeing the “file size” of this
200
+ /// data is rarely useful — I can’t think of a time when I’ve seen it and
201
+ /// learnt something. So we discard it and just output “-” instead.
202
+ ///
203
+ /// See this answer for more: http://unix.stackexchange.com/a/68266
204
+ None ,
205
+
206
+ /// This file is a block or character device, so instead of a size, print
207
+ /// out the file’s major and minor device IDs.
208
+ ///
209
+ /// This is what ls does as well. Without it, the devices will just have
210
+ /// file sizes of zero.
211
+ DeviceIDs ( DeviceIDs ) ,
212
+ }
213
+
214
+ /// The major and minor device IDs that gets displayed for device files.
215
+ ///
216
+ /// You can see what these device numbers mean:
217
+ /// - <http://www.lanana.org/docs/device-list/>
218
+ /// - <http://www.lanana.org/docs/device-list/devices-2.6+.txt>
219
+ #[ derive( Copy , Clone ) ]
220
+ pub struct DeviceIDs {
221
+ pub major : u8 ,
222
+ pub minor : u8 ,
223
+ }
224
+
225
+ /// This file’s size, if it’s a regular file.
226
+ ///
227
+ /// For directories, no size is given. Although they do have a size on
228
+ /// some filesystems, I’ve never looked at one of those numbers and gained
229
+ /// any information from it. So it’s going to be hidden instead.
230
+ ///
231
+ /// Block and character devices return their device IDs, because they
232
+ /// usually just have a file size of zero.
233
+ pub fn size ( metadata : & Metadata ) -> Size {
234
+ let filetype = metadata. file_type ( ) ;
235
+ if metadata. is_dir ( ) {
236
+ Size :: None
237
+ } else if filetype. is_char_device ( ) || filetype. is_block_device ( ) {
238
+ let device_ids = metadata. rdev ( ) . to_be_bytes ( ) ;
239
+
240
+ // In C-land, getting the major and minor device IDs is done with
241
+ // preprocessor macros called `major` and `minor` that depend on
242
+ // the size of `dev_t`, but we just take the second-to-last and
243
+ // last bytes.
244
+ Size :: DeviceIDs ( DeviceIDs {
245
+ major : device_ids[ 6 ] ,
246
+ minor : device_ids[ 7 ] ,
247
+ } )
248
+ } else {
249
+ Size :: Some ( metadata. len ( ) )
250
+ }
251
+ }
252
+
253
+ impl fmt:: Display for Size {
254
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
255
+ use number_prefix:: NumberPrefix ;
256
+ match self {
257
+ Size :: Some ( s) => match NumberPrefix :: decimal ( * s as f64 ) {
258
+ NumberPrefix :: Standalone ( n) => write ! ( f, "{}" , n) ,
259
+ NumberPrefix :: Prefixed ( p, n) => write ! ( f, "{:.1}{}" , n, p) ,
260
+ } ,
261
+ Size :: None => write ! ( f, "-" ) ,
262
+ Size :: DeviceIDs ( DeviceIDs { major, minor } ) => {
263
+ write ! ( f, "{},{}" , major, minor)
264
+ }
265
+ }
266
+ }
267
+ }
268
+ }
269
+
34
270
pub const MIN_AREA_WIDTH_FOR_PREVIEW : u16 = 72 ;
35
271
/// Biggest file size to preview in bytes
36
272
pub const MAX_FILE_SIZE_FOR_PREVIEW : u64 = 10 * 1024 * 1024 ;
@@ -93,10 +329,23 @@ impl FindFilePicker {
93
329
let dir1 = dir. clone ( ) ;
94
330
let mut picker = FilePicker :: new (
95
331
files,
332
+ // TODO: prevent this from running within score function that skews
333
+ // the score, and only calculate it once during initialization
96
334
move |path| {
97
335
let suffix = if path. is_dir ( ) { "/" } else { "" } ;
336
+ let metadata = fs:: metadata ( & * path) . unwrap ( ) ;
98
337
let path = path. strip_prefix ( & dir1) . unwrap_or ( path) . to_string_lossy ( ) ;
99
- path + suffix
338
+ let filetype = fields:: filetype ( & metadata) ;
339
+ let permissions = fields:: permissions ( & metadata) ;
340
+ let size = format ! ( "{}" , fields:: size( & metadata) ) ;
341
+ Cow :: Owned ( format ! (
342
+ "{:<22} {}{} {:>6}" ,
343
+ path + suffix, // TODO this should check for size and handle truncation
344
+ filetype,
345
+ permissions,
346
+ size,
347
+ // TODO add absolute/relative time? may need to handle truncation
348
+ ) )
100
349
} ,
101
350
|_cx, _path, _action| { } , // we use custom callback_fn
102
351
|_editor, path| Some ( ( path. clone ( ) , None ) ) ,
0 commit comments