├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── config.example.toml ├── src ├── config │ ├── commands.rs │ ├── keys.rs │ ├── mod.rs │ ├── options.rs │ └── theme.rs ├── core │ ├── app.rs │ └── mod.rs ├── error.rs ├── fs │ ├── metadata.rs │ └── mod.rs ├── lib.rs ├── main.rs ├── ui │ ├── mod.rs │ ├── multi_select.rs │ └── tab.rs └── utils │ ├── filter.rs │ ├── info.rs │ ├── logger.rs │ └── mod.rs └── theme.example.toml /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | /log/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | env: 4 | global: 5 | - PROJECT_NAME=marcos 6 | 7 | matrix: 8 | include: 9 | - os: linux 10 | rust: stable 11 | env: TARGET=x86_64-unknown-linux-gnu 12 | 13 | script: 14 | - cargo build --verbose --all 15 | - cargo test --verbose --all 16 | 17 | 18 | notifications: 19 | email: 20 | on_success: never 21 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "addr2line" 5 | version = "0.12.1" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "gimli 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "alphanumeric-sort" 13 | version = "1.0.13" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | 16 | [[package]] 17 | name = "ansi_term" 18 | version = "0.11.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 22 | ] 23 | 24 | [[package]] 25 | name = "arc-swap" 26 | version = "0.4.6" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [[package]] 30 | name = "array-macro" 31 | version = "1.0.5" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | 34 | [[package]] 35 | name = "arrayref" 36 | version = "0.3.6" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | 39 | [[package]] 40 | name = "arrayvec" 41 | version = "0.4.12" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | dependencies = [ 44 | "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 45 | ] 46 | 47 | [[package]] 48 | name = "arrayvec" 49 | version = "0.5.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | 52 | [[package]] 53 | name = "atty" 54 | version = "0.2.14" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | dependencies = [ 57 | "hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 60 | ] 61 | 62 | [[package]] 63 | name = "autocfg" 64 | version = "1.0.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | 67 | [[package]] 68 | name = "backtrace" 69 | version = "0.3.48" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | dependencies = [ 72 | "addr2line 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", 73 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 74 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "object 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 77 | ] 78 | 79 | [[package]] 80 | name = "base64" 81 | version = "0.11.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | 84 | [[package]] 85 | name = "bitflags" 86 | version = "1.2.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | 89 | [[package]] 90 | name = "blake2b_simd" 91 | version = "0.5.10" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | dependencies = [ 94 | "arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 96 | "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 97 | ] 98 | 99 | [[package]] 100 | name = "bytesize" 101 | version = "1.0.1" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | 104 | [[package]] 105 | name = "cfg-if" 106 | version = "0.1.10" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | 109 | [[package]] 110 | name = "chrono" 111 | version = "0.4.11" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | dependencies = [ 114 | "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", 117 | ] 118 | 119 | [[package]] 120 | name = "clap" 121 | version = "2.33.2" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | dependencies = [ 124 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 125 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 126 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 131 | ] 132 | 133 | [[package]] 134 | name = "cloudabi" 135 | version = "0.0.3" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | dependencies = [ 138 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 139 | ] 140 | 141 | [[package]] 142 | name = "constant_time_eq" 143 | version = "0.1.5" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | 146 | [[package]] 147 | name = "crossbeam-channel" 148 | version = "0.2.6" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | dependencies = [ 151 | "crossbeam-epoch 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 152 | "crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 154 | "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", 155 | "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 156 | ] 157 | 158 | [[package]] 159 | name = "crossbeam-epoch" 160 | version = "0.6.1" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | dependencies = [ 163 | "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 166 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 167 | "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 168 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 169 | ] 170 | 171 | [[package]] 172 | name = "crossbeam-utils" 173 | version = "0.5.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | 176 | [[package]] 177 | name = "crossbeam-utils" 178 | version = "0.6.6" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | dependencies = [ 181 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 182 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 183 | ] 184 | 185 | [[package]] 186 | name = "crossbeam-utils" 187 | version = "0.7.2" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | dependencies = [ 190 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 191 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 192 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 193 | ] 194 | 195 | [[package]] 196 | name = "cursive" 197 | version = "0.9.2" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | dependencies = [ 200 | "crossbeam-channel 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 201 | "enum-map 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 202 | "enumset 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 203 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 204 | "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 205 | "num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 206 | "owning_ref 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 207 | "signal-hook 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 208 | "termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 209 | "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 210 | "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 211 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 212 | "xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 213 | ] 214 | 215 | [[package]] 216 | name = "dirs" 217 | version = "1.0.5" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | dependencies = [ 220 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 221 | "redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 222 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 223 | ] 224 | 225 | [[package]] 226 | name = "enum-map" 227 | version = "0.4.1" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | dependencies = [ 230 | "array-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 231 | "enum-map-derive 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 232 | "reexport-proc-macro 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 233 | ] 234 | 235 | [[package]] 236 | name = "enum-map-derive" 237 | version = "0.4.3" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | dependencies = [ 240 | "proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", 241 | "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 242 | "syn 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 243 | ] 244 | 245 | [[package]] 246 | name = "enumset" 247 | version = "0.3.19" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | dependencies = [ 250 | "enumset_derive 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 251 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 252 | ] 253 | 254 | [[package]] 255 | name = "enumset_derive" 256 | version = "0.3.2" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | dependencies = [ 259 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 260 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 261 | "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", 262 | ] 263 | 264 | [[package]] 265 | name = "failure" 266 | version = "0.1.8" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | dependencies = [ 269 | "backtrace 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)", 270 | "failure_derive 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 271 | ] 272 | 273 | [[package]] 274 | name = "failure_derive" 275 | version = "0.1.8" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | dependencies = [ 278 | "proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", 279 | "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 280 | "syn 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 281 | "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", 282 | ] 283 | 284 | [[package]] 285 | name = "fern" 286 | version = "0.5.9" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | dependencies = [ 289 | "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 290 | "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 291 | ] 292 | 293 | [[package]] 294 | name = "fuchsia-cprng" 295 | version = "0.1.1" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | 298 | [[package]] 299 | name = "getrandom" 300 | version = "0.1.14" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | dependencies = [ 303 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 304 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 305 | "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", 306 | ] 307 | 308 | [[package]] 309 | name = "gimli" 310 | version = "0.21.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | 313 | [[package]] 314 | name = "hermit-abi" 315 | version = "0.1.13" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | dependencies = [ 318 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 319 | ] 320 | 321 | [[package]] 322 | name = "lazy_static" 323 | version = "1.4.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | 326 | [[package]] 327 | name = "libc" 328 | version = "0.2.71" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | 331 | [[package]] 332 | name = "lock_api" 333 | version = "0.1.5" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | dependencies = [ 336 | "owning_ref 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 337 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 338 | ] 339 | 340 | [[package]] 341 | name = "log" 342 | version = "0.4.11" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | dependencies = [ 345 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 346 | ] 347 | 348 | [[package]] 349 | name = "marcos" 350 | version = "0.1.0" 351 | dependencies = [ 352 | "alphanumeric-sort 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", 353 | "clap 2.33.2 (registry+https://github.com/rust-lang/crates.io-index)", 354 | "cursive 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", 355 | "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 356 | "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 357 | "failure_derive 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 358 | "fern 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", 359 | "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 360 | "mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 361 | "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", 362 | "serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", 363 | "systemstat 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 364 | "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 365 | "uname 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 366 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 367 | "users 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 368 | "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 369 | ] 370 | 371 | [[package]] 372 | name = "maybe-uninit" 373 | version = "2.0.0" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | 376 | [[package]] 377 | name = "memchr" 378 | version = "1.0.2" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | dependencies = [ 381 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 382 | ] 383 | 384 | [[package]] 385 | name = "memoffset" 386 | version = "0.2.1" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | 389 | [[package]] 390 | name = "mime" 391 | version = "0.3.16" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | 394 | [[package]] 395 | name = "mime_guess" 396 | version = "2.0.3" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | dependencies = [ 399 | "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 400 | "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 401 | ] 402 | 403 | [[package]] 404 | name = "nodrop" 405 | version = "0.1.14" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | 408 | [[package]] 409 | name = "nom" 410 | version = "3.2.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | dependencies = [ 413 | "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 414 | ] 415 | 416 | [[package]] 417 | name = "num" 418 | version = "0.2.1" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | dependencies = [ 421 | "num-complex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 422 | "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 423 | "num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 424 | "num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 425 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 426 | ] 427 | 428 | [[package]] 429 | name = "num-complex" 430 | version = "0.2.4" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | dependencies = [ 433 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 434 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 435 | ] 436 | 437 | [[package]] 438 | name = "num-integer" 439 | version = "0.1.42" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | dependencies = [ 442 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 443 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 444 | ] 445 | 446 | [[package]] 447 | name = "num-iter" 448 | version = "0.1.40" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | dependencies = [ 451 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 452 | "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 453 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 454 | ] 455 | 456 | [[package]] 457 | name = "num-rational" 458 | version = "0.2.4" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | dependencies = [ 461 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 462 | "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 463 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 464 | ] 465 | 466 | [[package]] 467 | name = "num-traits" 468 | version = "0.2.11" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | dependencies = [ 471 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 472 | ] 473 | 474 | [[package]] 475 | name = "numtoa" 476 | version = "0.1.0" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | 479 | [[package]] 480 | name = "object" 481 | version = "0.19.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | 484 | [[package]] 485 | name = "owning_ref" 486 | version = "0.4.1" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | dependencies = [ 489 | "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 490 | ] 491 | 492 | [[package]] 493 | name = "parking_lot" 494 | version = "0.6.4" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | dependencies = [ 497 | "lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 498 | "parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 499 | ] 500 | 501 | [[package]] 502 | name = "parking_lot_core" 503 | version = "0.3.1" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | dependencies = [ 506 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 507 | "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", 508 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 509 | "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 510 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 511 | ] 512 | 513 | [[package]] 514 | name = "proc-macro2" 515 | version = "0.4.30" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | dependencies = [ 518 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 519 | ] 520 | 521 | [[package]] 522 | name = "proc-macro2" 523 | version = "1.0.17" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | dependencies = [ 526 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 527 | ] 528 | 529 | [[package]] 530 | name = "quote" 531 | version = "0.6.13" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | dependencies = [ 534 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 535 | ] 536 | 537 | [[package]] 538 | name = "quote" 539 | version = "1.0.6" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | dependencies = [ 542 | "proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", 543 | ] 544 | 545 | [[package]] 546 | name = "rand" 547 | version = "0.5.6" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | dependencies = [ 550 | "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 551 | "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 552 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 553 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 554 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 555 | ] 556 | 557 | [[package]] 558 | name = "rand_core" 559 | version = "0.3.1" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | dependencies = [ 562 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 563 | ] 564 | 565 | [[package]] 566 | name = "rand_core" 567 | version = "0.4.2" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | 570 | [[package]] 571 | name = "redox_syscall" 572 | version = "0.1.56" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | 575 | [[package]] 576 | name = "redox_termios" 577 | version = "0.1.1" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | dependencies = [ 580 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 581 | ] 582 | 583 | [[package]] 584 | name = "redox_users" 585 | version = "0.3.4" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | dependencies = [ 588 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 589 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 590 | "rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 591 | ] 592 | 593 | [[package]] 594 | name = "reexport-proc-macro" 595 | version = "1.0.6" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | 598 | [[package]] 599 | name = "rust-argon2" 600 | version = "0.7.0" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | dependencies = [ 603 | "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 604 | "blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", 605 | "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 606 | "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 607 | ] 608 | 609 | [[package]] 610 | name = "rustc-demangle" 611 | version = "0.1.16" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | 614 | [[package]] 615 | name = "rustc_version" 616 | version = "0.2.3" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | dependencies = [ 619 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 620 | ] 621 | 622 | [[package]] 623 | name = "same-file" 624 | version = "1.0.6" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | dependencies = [ 627 | "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 628 | ] 629 | 630 | [[package]] 631 | name = "scopeguard" 632 | version = "0.3.3" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | 635 | [[package]] 636 | name = "semver" 637 | version = "0.9.0" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | dependencies = [ 640 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 641 | ] 642 | 643 | [[package]] 644 | name = "semver-parser" 645 | version = "0.7.0" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | 648 | [[package]] 649 | name = "serde" 650 | version = "1.0.110" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | 653 | [[package]] 654 | name = "serde_derive" 655 | version = "1.0.110" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | dependencies = [ 658 | "proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", 659 | "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 660 | "syn 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 661 | ] 662 | 663 | [[package]] 664 | name = "signal-hook" 665 | version = "0.1.15" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | dependencies = [ 668 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 669 | "signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 670 | ] 671 | 672 | [[package]] 673 | name = "signal-hook-registry" 674 | version = "1.2.0" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | dependencies = [ 677 | "arc-swap 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 678 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 679 | ] 680 | 681 | [[package]] 682 | name = "smallvec" 683 | version = "0.6.13" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | dependencies = [ 686 | "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 687 | ] 688 | 689 | [[package]] 690 | name = "stable_deref_trait" 691 | version = "1.1.1" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | 694 | [[package]] 695 | name = "strsim" 696 | version = "0.8.0" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | 699 | [[package]] 700 | name = "syn" 701 | version = "0.15.44" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | dependencies = [ 704 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 705 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 706 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 707 | ] 708 | 709 | [[package]] 710 | name = "syn" 711 | version = "1.0.27" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | dependencies = [ 714 | "proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", 715 | "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 716 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 717 | ] 718 | 719 | [[package]] 720 | name = "synstructure" 721 | version = "0.12.3" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | dependencies = [ 724 | "proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", 725 | "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 726 | "syn 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 727 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 728 | ] 729 | 730 | [[package]] 731 | name = "systemstat" 732 | version = "0.1.5" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | dependencies = [ 735 | "bytesize 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 736 | "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 737 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 738 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 739 | "nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 740 | "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", 741 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 742 | ] 743 | 744 | [[package]] 745 | name = "termion" 746 | version = "1.5.5" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | dependencies = [ 749 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 750 | "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 751 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 752 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 753 | ] 754 | 755 | [[package]] 756 | name = "textwrap" 757 | version = "0.11.0" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | dependencies = [ 760 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 761 | ] 762 | 763 | [[package]] 764 | name = "time" 765 | version = "0.1.43" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | dependencies = [ 768 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 769 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 770 | ] 771 | 772 | [[package]] 773 | name = "toml" 774 | version = "0.4.10" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | dependencies = [ 777 | "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", 778 | ] 779 | 780 | [[package]] 781 | name = "uname" 782 | version = "0.1.1" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | dependencies = [ 785 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 786 | ] 787 | 788 | [[package]] 789 | name = "unicase" 790 | version = "2.6.0" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | dependencies = [ 793 | "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", 794 | ] 795 | 796 | [[package]] 797 | name = "unicode-segmentation" 798 | version = "1.6.0" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | 801 | [[package]] 802 | name = "unicode-width" 803 | version = "0.1.7" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | 806 | [[package]] 807 | name = "unicode-xid" 808 | version = "0.1.0" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | 811 | [[package]] 812 | name = "unicode-xid" 813 | version = "0.2.0" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | 816 | [[package]] 817 | name = "users" 818 | version = "0.8.1" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | dependencies = [ 821 | "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", 822 | ] 823 | 824 | [[package]] 825 | name = "vec_map" 826 | version = "0.8.2" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | 829 | [[package]] 830 | name = "version_check" 831 | version = "0.9.2" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | 834 | [[package]] 835 | name = "walkdir" 836 | version = "2.3.1" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | dependencies = [ 839 | "same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 840 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 841 | "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 842 | ] 843 | 844 | [[package]] 845 | name = "wasi" 846 | version = "0.9.0+wasi-snapshot-preview1" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | 849 | [[package]] 850 | name = "winapi" 851 | version = "0.3.8" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | dependencies = [ 854 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 855 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 856 | ] 857 | 858 | [[package]] 859 | name = "winapi-i686-pc-windows-gnu" 860 | version = "0.4.0" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | 863 | [[package]] 864 | name = "winapi-util" 865 | version = "0.1.5" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | dependencies = [ 868 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 869 | ] 870 | 871 | [[package]] 872 | name = "winapi-x86_64-pc-windows-gnu" 873 | version = "0.4.0" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | 876 | [[package]] 877 | name = "xi-unicode" 878 | version = "0.1.0" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | 881 | [metadata] 882 | "checksum addr2line 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" 883 | "checksum alphanumeric-sort 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "0d338a8e22f0d04e4f44b0915c537d4dfa2e002ffa7ef78364de604d180b0448" 884 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 885 | "checksum arc-swap 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62" 886 | "checksum array-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "06e97b4e522f9e55523001238ac59d13a8603af57f69980de5d8de4bbbe8ada6" 887 | "checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 888 | "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" 889 | "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 890 | "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 891 | "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 892 | "checksum backtrace 0.3.48 (registry+https://github.com/rust-lang/crates.io-index)" = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" 893 | "checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 894 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 895 | "checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" 896 | "checksum bytesize 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "81a18687293a1546b67c246452202bbbf143d239cb43494cc163da14979082da" 897 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 898 | "checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" 899 | "checksum clap 2.33.2 (registry+https://github.com/rust-lang/crates.io-index)" = "10040cdf04294b565d9e0319955430099ec3813a64c952b86a41200ad714ae48" 900 | "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 901 | "checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 902 | "checksum crossbeam-channel 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7b85741761b7f160bc5e7e0c14986ef685b7f8bf9b7ad081c60c604bb4649827" 903 | "checksum crossbeam-epoch 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2449aaa4ec7ef96e5fb24db16024b935df718e9ae1cec0a1e68feeca2efca7b8" 904 | "checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015" 905 | "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" 906 | "checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 907 | "checksum cursive 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1df013f020cf1e66c456c9af584ae660590b8147186fd66b941604f85145b880" 908 | "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" 909 | "checksum enum-map 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "caa1769f019df7ccd8f9a741d2d608309688d0f1bd8a8747c14ac993660c761c" 910 | "checksum enum-map-derive 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e57001dfb2532f5a103ff869656887fae9a8defa7d236f3e39d2ee86ed629ad7" 911 | "checksum enumset 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "43bd5effaae6a671efa2032056110916a501bd24128cfb6f44e5a339b5cdb152" 912 | "checksum enumset_derive 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f73e5c77cf68e532b0e6fdd22c7f8f4d09f6f663692aecca0b3d8ec2e11af723" 913 | "checksum failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" 914 | "checksum failure_derive 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" 915 | "checksum fern 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e69ab0d5aca163e388c3a49d284fed6c3d0810700e77c5ae2756a50ec1a4daaa" 916 | "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 917 | "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 918 | "checksum gimli 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" 919 | "checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" 920 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 921 | "checksum libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" 922 | "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" 923 | "checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 924 | "checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 925 | "checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" 926 | "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" 927 | "checksum mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 928 | "checksum mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" 929 | "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 930 | "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" 931 | "checksum num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" 932 | "checksum num-complex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" 933 | "checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" 934 | "checksum num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" 935 | "checksum num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" 936 | "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 937 | "checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 938 | "checksum object 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" 939 | "checksum owning_ref 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" 940 | "checksum parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0802bff09003b291ba756dc7e79313e51cc31667e94afbe847def490424cde5" 941 | "checksum parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad7f7e6ebdc79edff6fdcb87a55b620174f7a989e3eb31b65231f4af57f00b8c" 942 | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 943 | "checksum proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" 944 | "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 945 | "checksum quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" 946 | "checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" 947 | "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 948 | "checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 949 | "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 950 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 951 | "checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" 952 | "checksum reexport-proc-macro 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b90ec417f693152463d468b6d06ccc45ae3833f0538ef9e1cc154cf09eb1f575" 953 | "checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" 954 | "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 955 | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 956 | "checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 957 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 958 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 959 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 960 | "checksum serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" 961 | "checksum serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" 962 | "checksum signal-hook 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff2db2112d6c761e12522c65f7768548bd6e8cd23d2a9dae162520626629bd6" 963 | "checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" 964 | "checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" 965 | "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" 966 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 967 | "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 968 | "checksum syn 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "ef781e621ee763a2a40721a8861ec519cb76966aee03bb5d00adb6a31dc1c1de" 969 | "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" 970 | "checksum systemstat 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2078da8d09c6202bffd5e075946e65bfad5ce2cfa161edb15c5f014a8440adee" 971 | "checksum termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905" 972 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 973 | "checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 974 | "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" 975 | "checksum uname 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" 976 | "checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 977 | "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 978 | "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" 979 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 980 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 981 | "checksum users 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fed7d0912567d35f88010c23dbaf865e9da8b5227295e8dc0f2fdd109155ab7" 982 | "checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 983 | "checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 984 | "checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 985 | "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 986 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 987 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 988 | "checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 989 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 990 | "checksum xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "12ea8eda4b1eb72f02d148402e23832d56a33f55d8c1b2d5bcdde91d79d47cb1" 991 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "marcos" 3 | version = "0.1.0" 4 | authors = ["Hitesh Paul "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | path = "src/main.rs" 9 | name = "mcf" 10 | 11 | [dependencies] 12 | toml = "0.4" 13 | serde = "1.0" 14 | serde_derive = "1.0" 15 | log = "0.4" 16 | fern = "0.5" 17 | clap = "2" 18 | walkdir = "2.2.6" 19 | alphanumeric-sort = "1.0.3" 20 | mime_guess = "2.0.0-alpha.6" 21 | uname = "0.1.1" 22 | users = "0.8.0" 23 | systemstat = "0.1.3" 24 | failure = "0.1.2" 25 | failure_derive = "0.1.3" 26 | dirs = "1.0.5" 27 | unicode-width = "0.1.5" 28 | 29 | [dependencies.cursive] 30 | version = "0.9" 31 | default-features = false 32 | features = ["termion-backend"] 33 | 34 | 35 | # Some crates to consider 36 | # fsort 37 | # tokio-fs 38 | # sys-info 39 | # walkdir 40 | # fs_extra 41 | # fs_swap 42 | # alphanumeric-sort 43 | # path_abs 44 | # rexiv2 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Hitesh Paul 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Marcos [![Build Status](https://travis-ci.org/TerminalWitchcraft/marcos.svg?branch=master)](https://travis-ci.org/TerminalWitchcraft/marcos) [![GitHub license](https://img.shields.io/github/license/TerminalWitchcraft/marcos.svg)](https://github.com/TerminalWitchcraft/marcos/blob/master/LICENSE) 2 | 3 | Command line file manager in Rust with VIM-inspired keybindings. 4 | 5 | **NOTE!!** This is still in Work in Progress and not usable for daily use until version `0.1.0` is released. 6 | 7 | ## Design Goals 8 | 9 | * Fast: The application should be snappier at startup times after reading all necessary configs, loading resources, etc. 10 | * Fully operational: _Most_ of the features of a morden file manager should be implemented, although support for uncommon features should be offloaded to scripts/plugins 11 | * Keyboard Driven: The whole idea of the project is based upon the keyboard driven approach inspired from _vim_. 12 | * Functional: Composition/chaining of vim-like commands based on your existing muscle memory, to just get things done. I am not inclined towards defining new set of keybindings/commands until unless it is absolutely necessary! 13 | 14 | ## Possible future goals 15 | 16 | * 24-bit Image: Preview image with the help of w3mimg(or possibly [ueberzug](https://github.com/seebye/ueberzug)). 17 | * Async: Make some of the core operations non-blocking 18 | * Extensibility: Marcos could be extended with the help of plugins 19 | * Support for lua/python plugins: Plugins can be written in lua/python languages 20 | 21 | 22 | ## Key bindings 23 | 24 | Only some key bindings are implemented as of now. All mentioned key bindings will be implemented before `0.1.0` release. 25 | 26 | | Key | Action | 27 | |----------|---------------------------------------------------------------------------------------| 28 | | q | Exit marcos | 29 | | j | Select item down | 30 | | k | Select item up | 31 | | h | Go previous (left) | 32 | | l | Go next(right) | 33 | | : | Activate command mode | 34 | | gg | Go to the first selection | 35 | | G | Go to the last selection | 36 | | [count]G | Go to the [count] item | 37 | | za | Toggle visibility of hidden items | 38 | | y | Yank(Copy) the selected file/folder(Similar to Ctrl-c) | 39 | | x | Cut the selected file/folder(similar to Ctrl-x) | 40 | | p | Paste the Copied/Cut file/folder(Similar to Ctrl-v) | 41 | | r | Rename selected file/folder | 42 | | dd | Delete selected file/folder(with confirmation) | 43 | | o | Create new file(`touch filename`) | 44 | | O | Create new directory (`mkdir dirname`) | 45 | | P | Paste the Copied/Cut file/folder replacing existing with same name(with Confirmation) | 46 | | mX | Create a bookmark with name X | 47 | | `X | Jump to bookmark with name X | 48 | | n | Move to next match | 49 | | N | Move to previous match | 50 | | / | Search | 51 | | v | Starts visual mode, selects all files until you press ESC | 52 | | V | Visual mode, select all | 53 | | Ctrl+r | Refresh(listings, data, cache, etc) | 54 | | ESC | Get me out! | 55 | 56 | 57 | 58 | ## Architecture 59 | 60 | The best way to learn and know about the structure is by reading the docs by running: 61 | ``` 62 | cargo doc --open 63 | ``` 64 | 65 | Besides, I try my best to include relevant comments and TODO[detail] tags. 66 | -------------------------------------------------------------------------------- /config.example.toml: -------------------------------------------------------------------------------- 1 | # Author: Hitesh Paul 2 | # Example configuration file for Marcos file manager(https://github.com/TerminalWitchcraft/marcos) 3 | # All options are optional. If any option is missing, the default value is loaded at runtime. 4 | 5 | 6 | [Options] 7 | 8 | # Metric to display size of various items. Possible values "bits", "bytes". 9 | size = "bits" 10 | 11 | # Show hidden files? Possible values: true, false 12 | show_hidden = false 13 | 14 | # Ask for confirmation when deleting modifying on disk? Possible values true, false 15 | confirm = true 16 | 17 | # Preview images? Possible values true, false 18 | show_images = false 19 | 20 | # Status bar position. Possible values: "top", "bottom" 21 | status_position = "bottom" 22 | 23 | # Shorten title. Possible value: any positive number which defines the number 24 | # of directories to show. If set to 0, full path will be shown 25 | shorten_title = 0 26 | 27 | # [TODO] Sort. Possible values: "default", "size", "type", "atime" 28 | # sort = "default" 29 | 30 | # Preview max file size. Files larger than this size will not be previewed. 31 | # Value in bytes. 32 | preview_max_size = 102400 33 | 34 | # Delay in ms. Marcos updates directories after the specified value. 35 | delay_idle = 2000 36 | 37 | # Show line number? Possible value: true, false 38 | line_numbers = false 39 | 40 | # Show popup windows on confirmation?[Defaults to status_bar] Possible values: true, false 41 | show_popup = true 42 | 43 | # Display disk usage at specified mount point with display name. 44 | [mount.rootfs] 45 | point = "/" 46 | 47 | 48 | 49 | [KeyMaps] 50 | # TODO 51 | quit = "q" 52 | select_up = "k" 53 | select_down = "j" 54 | # select_first = "gg" 55 | # select_last = "G" 56 | # select_n = "*g" 57 | back = "h" 58 | forward = "l" 59 | prompt = ":" 60 | show_hidden = "za" 61 | yank = "y" 62 | cut = "x" 63 | paste = "p" 64 | paste_replace = "P" 65 | rename = "r" 66 | delete_with_cfm = "dd" 67 | new_file = "o" 68 | new_folder = "O" 69 | # create bookmark 70 | # jumpto bookmark 71 | search = "/" 72 | next_match = "n" 73 | previous_match = "N" 74 | visual = "v" 75 | visual_all = "V" 76 | refresh = "C-r" 77 | -------------------------------------------------------------------------------- /src/config/commands.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerminalWitchcraft/marcos/856c6a41e22319d52752927e9423cb36db670a1a/src/config/commands.rs -------------------------------------------------------------------------------- /src/config/keys.rs: -------------------------------------------------------------------------------- 1 | use cursive::event::Event; 2 | use cursive::event::Key; 3 | use cursive::Cursive; 4 | 5 | use crate::error::*; 6 | 7 | enum Modifier { 8 | // Shift is not included, as Capital letter denotes shift key usage! 9 | Alt, 10 | Ctrl, 11 | Shift, 12 | AltCtrl, 13 | AltShift, 14 | CtrlShift, 15 | NoMod, 16 | // Currently Meta bindings not supported by Cursive 17 | // Meta 18 | } 19 | 20 | pub struct KeySequence { 21 | takes_count: bool, 22 | max_count: Option, 23 | modifier: Modifier, 24 | key: Vec, 25 | operation: fn(&mut Cursive), 26 | } 27 | 28 | impl KeySequence { 29 | /// Funtion to emit a vector of `Event`s. 30 | fn emit_sequence(self) -> (bool, Vec) { 31 | let mut seq: Vec = Vec::with_capacity(2); 32 | match self { 33 | KeySequence { 34 | modifier: Modifier::Alt, 35 | key: c, 36 | .. 37 | } => { 38 | if !c.is_empty() { 39 | seq.push(Event::AltChar(c[0])) 40 | } 41 | } 42 | KeySequence { 43 | modifier: Modifier::Ctrl, 44 | key: c, 45 | .. 46 | } => { 47 | if !c.is_empty() { 48 | seq.push(Event::CtrlChar(c[0])) 49 | } 50 | } 51 | KeySequence { 52 | takes_count: _, 53 | modifier: Modifier::Shift, 54 | key: c, 55 | .. 56 | } => { 57 | if !c.is_empty() { 58 | seq.push(Event::Char(c[0].to_ascii_uppercase())) 59 | } 60 | } 61 | // KeySequence{takes_count, 62 | // modifier:Modifier::AltCtrl, 63 | // key:c} => {seq.push(Event::CtrlAlt(_))}, 64 | // KeySequence{takes_count, 65 | // modifier:Modifier::AltShift, 66 | // key:c} => {seq.push(Event::CtrlAlt(c))}, 67 | // KeySequence{takes_count, 68 | // modifier:Modifier::CtrlShift, 69 | // key:c} => {seq.push(Event::AltChar(c))}, 70 | KeySequence { 71 | takes_count: _, 72 | modifier: Modifier::NoMod, 73 | key: c, 74 | .. 75 | } => { 76 | for i in c { 77 | seq.push(Event::Char(i)) 78 | } 79 | } 80 | _ => {} 81 | } 82 | (self.takes_count, seq) 83 | } 84 | } 85 | 86 | pub enum KeyBindings { 87 | Quit(KeySequence), 88 | SelectUp(KeySequence), 89 | SelectDown(KeySequence), 90 | Back(KeySequence), 91 | Forward(KeySequence), 92 | Console(KeySequence), 93 | SelectFirst(KeySequence), 94 | SelectLast(KeySequence), 95 | SelectN(KeySequence), 96 | ShowHidden(KeySequence), 97 | Yank(KeySequence), 98 | Cut(KeySequence), 99 | Paste(KeySequence), 100 | PasteReplace(KeySequence), 101 | Rename(KeySequence), 102 | DeleteWithConfirm(KeySequence), 103 | NewFile(KeySequence), 104 | NewDir(KeySequence), 105 | CreateBookmark(KeySequence), 106 | JumpToBookmark(KeySequence), 107 | Search(KeySequence), 108 | NextMatch(KeySequence), 109 | PrevMatch(KeySequence), 110 | Visual(KeySequence), 111 | VisualAll(KeySequence), 112 | Refresh(KeySequence), 113 | } 114 | 115 | #[derive(Serialize, Deserialize)] 116 | pub struct KeyMaps { 117 | quit: String, 118 | select_up: String, 119 | select_down: String, 120 | // select_first = "gg" 121 | // select_last = "G" 122 | // select_n = "*g" 123 | back: String, 124 | forward: String, 125 | prompt: String, 126 | show_hidden: String, 127 | yank: String, 128 | cut: String, 129 | paste: String, 130 | paste_replace: String, 131 | rename: String, 132 | delete_with_cfm: String, 133 | new_file: String, 134 | new_folder: String, 135 | // create bookmark 136 | // jumpto bookmark 137 | search: String, 138 | next_match: String, 139 | previous_match: String, 140 | visual: String, 141 | visual_all: String, 142 | refresh: String, 143 | } 144 | 145 | impl Default for KeyMaps { 146 | fn default() -> Self { 147 | Self { 148 | quit: "q".to_string(), 149 | select_up: "k".to_string(), 150 | select_down: "j".to_string(), 151 | // select_first = "gg" 152 | // select_last = "G" 153 | // select_n = "*g" 154 | back: "h".to_string(), 155 | forward: "l".to_string(), 156 | prompt: ":".to_string(), 157 | show_hidden: "za".to_string(), 158 | yank: "y".to_string(), 159 | cut: "x".to_string(), 160 | paste: "p".to_string(), 161 | paste_replace: "P".to_string(), 162 | rename: "r".to_string(), 163 | delete_with_cfm: "dd".to_string(), 164 | new_file: "o".to_string(), 165 | new_folder: "O".to_string(), 166 | // create bookmark 167 | // jumpto bookmark 168 | search: "/".to_string(), 169 | next_match: "n".to_string(), 170 | previous_match: "N".to_string(), 171 | visual: "v".to_string(), 172 | visual_all: "V".to_string(), 173 | refresh: "C-r".to_string(), 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod keys; 2 | pub mod options; 3 | use crate::error::*; 4 | 5 | use dirs; 6 | use std::fs as stdfs; 7 | use std::path::PathBuf; 8 | use toml; 9 | 10 | #[derive(Serialize, Deserialize)] 11 | pub struct Config { 12 | #[serde(default)] 13 | KeyMaps: keys::KeyMaps, 14 | #[serde(default)] 15 | Options: options::ConfigOptions, 16 | } 17 | 18 | impl Config { 19 | pub fn load() -> Result { 20 | let data_path: PathBuf = dirs::config_dir().ok_or(ErrorKind::DirNotFound { 21 | dirname: String::from("CONFIG_DIR"), 22 | })?; 23 | let data_path = data_path.join("marcos"); 24 | if !data_path.exists() { 25 | stdfs::create_dir_all(&data_path).expect("Cannot create data_dir"); 26 | } 27 | let config_file = data_path.join("config.toml"); 28 | debug!("Loading theme from file: {:?}", config_file); 29 | if !config_file.is_file() { 30 | stdfs::File::create(&config_file).expect("Failed to create asset file"); 31 | } 32 | let config_str = stdfs::read_to_string(config_file)?; 33 | let config_data: Config = toml::from_str(config_str.as_str())?; 34 | Ok(config_data) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/config/options.rs: -------------------------------------------------------------------------------- 1 | #[derive(Serialize, Deserialize)] 2 | enum Size { 3 | Bits, 4 | Bytes, 5 | } 6 | 7 | #[derive(Serialize, Deserialize)] 8 | enum StatusPosition { 9 | Top, 10 | Bottom, 11 | } 12 | 13 | #[derive(Serialize, Deserialize)] 14 | pub struct ConfigOptions { 15 | size: Size, 16 | show_hidden: bool, 17 | confirm: bool, 18 | show_images: bool, 19 | status_position: StatusPosition, 20 | shorten_title: usize, 21 | preview_max_size: usize, 22 | delay_idle: usize, 23 | line_numbers: bool, 24 | show_popup: bool, 25 | } 26 | 27 | impl Default for ConfigOptions { 28 | fn default() -> ConfigOptions { 29 | debug!("Loading defaults for ConfigOptions"); 30 | ConfigOptions { 31 | size: Size::Bytes, 32 | show_hidden: false, 33 | confirm: true, 34 | show_images: false, 35 | status_position: StatusPosition::Bottom, 36 | shorten_title: 0, 37 | preview_max_size: 102400, 38 | delay_idle: 2000, 39 | line_numbers: false, 40 | show_popup: false, 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/config/theme.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerminalWitchcraft/marcos/856c6a41e22319d52752927e9423cb36db670a1a/src/config/theme.rs -------------------------------------------------------------------------------- /src/core/app.rs: -------------------------------------------------------------------------------- 1 | //! Module contains functions related to core functionalities of the app. 2 | 3 | use std::cell::RefCell; 4 | use std::collections::HashMap; 5 | use std::env; 6 | use std::fs as stdfs; 7 | use std::path::PathBuf; 8 | use std::process; 9 | use std::rc::Rc; 10 | 11 | use cursive::event::{Event, EventResult, Key}; 12 | #[allow(unused_imports)] 13 | use cursive::traits::{Boxable, Identifiable, Scrollable}; 14 | use cursive::views::*; 15 | use cursive::align::*; 16 | use cursive::theme::*; 17 | use cursive::view::Position; 18 | use cursive::Cursive; 19 | 20 | use dirs; 21 | 22 | use alphanumeric_sort::compare_os_str; 23 | use walkdir; 24 | use walkdir::WalkDir; 25 | 26 | use mime_guess::guess_mime_type; 27 | use mime_guess::Mime; 28 | 29 | use crate::config; 30 | use crate::error::*; 31 | use crate::fs::Entry; 32 | use crate::ui::MultiSelectView; 33 | use crate::ui::Tab; 34 | use crate::utils::{filter, info, logger}; 35 | 36 | /// Create a new instance of marcos with the specified backend. 37 | /// 38 | /// It also setups the logger for log events 39 | pub fn init(path: &str, log_file: Option<&str>, log_level: Option<&str>) -> Result { 40 | logger::init(log_file, log_level)?; 41 | let path = match path { 42 | "." | "./" => env::current_dir()?, 43 | "../" | ".." => env::current_dir()?.parent().unwrap().to_path_buf(), 44 | x => PathBuf::from(x), 45 | }; 46 | info!("Initializing with path {:?}", path); 47 | if !path.is_dir() { 48 | debug!("Failure with directory {:?}", path); 49 | println!("Incorrect path or unaccessible directory! Please cheack PATH"); 50 | process::exit(1); 51 | } 52 | let _app_config = config::Config::load(); 53 | let mut app = App::new()?; 54 | app.add_tab(1, path)?; 55 | app.load_bindings(); 56 | Ok(app) 57 | } 58 | 59 | /// The data structure holding various elements related to `App`. 60 | #[allow(dead_code)] 61 | pub struct App { 62 | /// The main application, the cursive instance. 63 | pub siv: Cursive, 64 | /// The vector of tabs 65 | // pub vec_tabs: HashMap, 66 | pub vec_tabs: Rc>>, 67 | /// The index of focused tab starting from 0. 68 | focused_tab: usize, 69 | /// The index of focused entry starting from 0. 70 | focused_entry: usize, 71 | } 72 | 73 | impl App { 74 | /// Create a new instance of cursive with default global callbacks. 75 | /// `q` is used to quit the cursive instance. 76 | /// 77 | /// TODO `:` is used to open the command box 78 | pub fn new() -> Result { 79 | let data_path: PathBuf = dirs::config_dir().ok_or(ErrorKind::DirNotFound { 80 | dirname: String::from("CONFIG_DIR"), 81 | })?; 82 | let data_path = data_path.join("marcos"); 83 | if !data_path.exists() { 84 | stdfs::create_dir_all(&data_path).expect("Cannot create data_dir"); 85 | } 86 | let asset_file = data_path.join("style.toml"); 87 | debug!("Loading theme from file: {:?}", asset_file); 88 | if !asset_file.is_file() { 89 | stdfs::File::create(&asset_file).expect("Failed to create asset file"); 90 | } 91 | let mut siv = Cursive::default(); 92 | 93 | // Create empty views 94 | let p_widget = MultiSelectView::::new().with_id("parent"); 95 | let c_widget = MultiSelectView::::new().on_select(update_info); 96 | let c_widget = OnEventView::new(c_widget).with_id("current"); 97 | let preview_widget = TextView::new("").with_id("preview"); 98 | let top_widget = LinearLayout::horizontal() 99 | .child(TextView::new(info::user_info()) 100 | .h_align(HAlign::Left) 101 | .with_id("topbar/left")) 102 | .child(TextView::new(info::user_info()) 103 | .h_align(HAlign::Left) 104 | .with_id("topbar/center").full_width()) 105 | .child(TextView::new(info::disk_info("/")) 106 | .h_align(HAlign::Right) 107 | .with_id("topbar/right").full_width()); 108 | // let top_bar = TextView::new(format!("{} {}", info::user_info(), info::disk_info("/"))) 109 | // .with_id("topbar"); 110 | let mut status_bar = HideableView::new(TextView::new("Status").with_id("status")); 111 | status_bar.unhide(); 112 | // let console = EditView::new().filler(">").with_id("console"); 113 | // let console = HideableView::new(console); 114 | // let console = console.with_id("console/vis"); 115 | let status_bar = status_bar.with_id("status/vis"); 116 | 117 | // Horizontal panes 118 | let mut panes = LinearLayout::horizontal(); 119 | panes.add_child( 120 | Panel::new(p_widget) 121 | .full_width() 122 | .max_width(30) 123 | .full_height(), 124 | ); 125 | panes.add_child( 126 | Panel::new(c_widget) 127 | .full_width() 128 | .max_width(40) 129 | .full_height(), 130 | ); 131 | panes.add_child(Panel::new(preview_widget).full_width().full_height()); 132 | let h_panes = LinearLayout::vertical() 133 | .child(top_widget.full_width()) 134 | .child(panes) 135 | // .child(console) 136 | .child(status_bar); 137 | 138 | siv.add_layer(h_panes); 139 | siv.add_global_callback(Event::CtrlChar('w'), |s| s.quit()); 140 | siv.add_global_callback('q', |s| s.quit()); 141 | let vec_tabs = Rc::new(RefCell::new(HashMap::::new())); 142 | 143 | debug!("Loading theme resource file"); 144 | siv.load_theme_file(asset_file).expect("Cannot find file!"); 145 | Ok(Self { 146 | siv, 147 | vec_tabs, 148 | focused_entry: 0, 149 | focused_tab: 0, 150 | }) 151 | } 152 | 153 | /// Funtion to load key-bindings. 154 | pub fn load_bindings(&mut self) { 155 | let v_clone = self.vec_tabs.clone(); 156 | self.siv.add_global_callback('h', move |s: &mut Cursive| { 157 | debug!("Inside global callback h"); 158 | // Get current_view selection index 159 | let mut current_selection = None; 160 | s.call_on_id( 161 | "current", 162 | |event_view: &mut OnEventView>| { 163 | let view = event_view.get_inner(); 164 | current_selection = view.selected_id(); 165 | }, 166 | ); 167 | if let Some(mut tab) = v_clone.borrow_mut().get_mut(&1) { 168 | tab.focused 169 | .insert(PathBuf::from(&tab.c_view), current_selection.unwrap_or(0)); 170 | tab.go_back(); 171 | // tab.c_focused = current_selection; 172 | App::update_tab(s, &mut tab); 173 | s.call_on_id("topbar/center", |view: &mut TextView| { 174 | let mut text: TextContent = view.get_shared_content(); 175 | text.set_content(format!(" {}", tab.c_view.to_str().unwrap())); 176 | }); 177 | }; 178 | }); 179 | 180 | let v_clone2 = self.vec_tabs.clone(); 181 | self.siv.add_global_callback('l', move |s: &mut Cursive| { 182 | s.call_on_id( 183 | "current", 184 | |event_view: &mut OnEventView>| { 185 | let event = event_view.get_inner_mut(); 186 | if let Some(path) = event.selection() { 187 | if path.is_dir() { 188 | if let Some(mut tab) = v_clone2.borrow_mut().get_mut(&1) { 189 | debug!("Moving forward to path {:?}", path); 190 | tab.go_forward(path.to_path_buf()); 191 | }; 192 | } // if 193 | }; 194 | }, 195 | ); 196 | if let Some(mut tab) = v_clone2.borrow_mut().get_mut(&1) { 197 | App::update_tab(s, &mut tab); 198 | s.call_on_id("topbar/center", |view: &mut TextView| { 199 | let mut text = view.get_shared_content(); 200 | text.set_content(format!(" {}",tab.c_view.to_str().unwrap())); 201 | }); 202 | } 203 | }); 204 | 205 | self.siv.call_on_id( 206 | "current", 207 | |event_view: &mut OnEventView>| { 208 | event_view.set_on_pre_event_inner('k', |s| { 209 | let cb = s.select_up(1); 210 | Some(EventResult::Consumed(Some(cb))) 211 | }); 212 | event_view.set_on_pre_event_inner('j', |s| { 213 | let cb = s.select_down(1); 214 | Some(EventResult::Consumed(Some(cb))) 215 | }); 216 | }, 217 | ); 218 | 219 | self.siv.add_global_callback('/', |c: &mut Cursive| { 220 | debug!("You pressed search key"); 221 | c.add_layer(create_console("/")); 222 | let s = c.screen_mut(); 223 | let l = LayerPosition::FromFront(0); 224 | let pos = s.offset().saturating_add((9000, 9000)); 225 | let p = Position::absolute(pos); 226 | s.reposition_layer(l, p); 227 | }); 228 | 229 | self.siv.add_global_callback('?', |c: &mut Cursive| { 230 | debug!("You pressed search key"); 231 | c.add_layer(create_console("?")); 232 | let s = c.screen_mut(); 233 | let l = LayerPosition::FromFront(0); 234 | let pos = s.offset().saturating_add((9000, 9000)); 235 | let p = Position::absolute(pos); 236 | s.reposition_layer(l, p); 237 | }); 238 | 239 | // Cancels current action. 240 | self.siv.add_global_callback(Event::Key(Key::Esc), |s: &mut Cursive| { 241 | let mut exists: bool = false; 242 | { 243 | let stack_view = s.screen_mut(); 244 | if let Some(_data) = stack_view.get(LayerPosition::FromBack(1)) { 245 | debug!("Multiple layer found"); 246 | exists = true; 247 | } 248 | } 249 | if exists {s.pop_layer();} 250 | }); 251 | } 252 | 253 | /// [Experimental] Adds a new tab to the main view. Currently only single tab is supported 254 | /// for the sake of simplicity. Multiple tabs support will land in near future. 255 | pub fn add_tab(&mut self, name: u32, path: PathBuf) -> Result<()> { 256 | let mut tab = Tab::from(name, &path)?; 257 | self.siv.call_on_id("topbar/center", |view: &mut TextView| { 258 | let mut current_text: TextContent = view.get_shared_content(); 259 | current_text.set_content(format!(" {}", path.to_str().unwrap())); 260 | }); 261 | self.siv.call_on_id( 262 | "current", 263 | |event_view: &mut OnEventView>| { 264 | let view = event_view.get_inner_mut(); 265 | view.clear(); 266 | for entry in App::get_path_iter(&tab.c_view) 267 | .filter_entry(|e| e.path().is_dir() && !filter::is_hidden(e)) 268 | { 269 | let entry = entry.unwrap(); 270 | match entry.file_name().to_str() { 271 | Some(c) => { 272 | view.add_item(format!(r"  {}", c), PathBuf::from(entry.path())) 273 | } 274 | None => {} 275 | } 276 | } 277 | for entry in App::get_path_iter(&tab.c_view) 278 | .filter_entry(|e| e.path().is_file() && !filter::is_hidden(e)) 279 | { 280 | let entry = entry.unwrap(); 281 | match entry.file_name().to_str() { 282 | Some(c) => { 283 | view.add_item(format!(r"  {}", c), PathBuf::from(entry.path())) 284 | } 285 | None => {} 286 | }; 287 | } 288 | view.set_selection(0); 289 | }, 290 | ); 291 | 292 | let mut i: usize = 0; 293 | self.siv 294 | .call_on_id("parent", |view: &mut MultiSelectView| { 295 | view.clear(); 296 | debug!("siv call on id parent {:?}", tab.p_view.to_str()); 297 | match tab.p_view.to_str() { 298 | Some("root") => { 299 | view.add_item("/", PathBuf::from("/")); 300 | view.set_enabled(false); 301 | view.set_selection(0); 302 | } 303 | Some(_) | None => { 304 | for (index, entry) in App::get_path_iter(&tab.p_view) 305 | .filter_entry(|e| e.path().is_dir() && !filter::is_hidden(e)) 306 | .enumerate() 307 | { 308 | let entry = entry.unwrap(); 309 | if entry.path() == &tab.c_view { 310 | i = index; 311 | } 312 | match entry.file_name().to_str() { 313 | Some(c) => view 314 | .add_item(format!("  {}", c), PathBuf::from(entry.path())), 315 | None => {} 316 | }; 317 | } 318 | for entry in App::get_path_iter(&tab.p_view) 319 | .filter_entry(|e| e.path().is_file() && !filter::is_hidden(e)) 320 | { 321 | let entry = entry.unwrap(); 322 | match entry.file_name().to_str() { 323 | Some(c) => view 324 | .add_item(format!("  {}", c), PathBuf::from(entry.path())), 325 | None => {} 326 | }; 327 | } 328 | view.set_selection(i); 329 | view.set_enabled(false); 330 | } // None 331 | }; 332 | }); 333 | tab.focused.insert(PathBuf::from(&tab.p_view), i); 334 | // tab.p_focused = i; 335 | debug!("Value of tab: {:?}", tab); 336 | self.vec_tabs.borrow_mut().insert(1, tab); 337 | self.focused_entry = i; 338 | debug!("Value of arr: {:?}", self.vec_tabs.borrow()); 339 | Ok(()) 340 | } 341 | 342 | /// Funtion which updates the content of `Tab` when you go forward or 343 | /// backward in a hierarchy. 344 | fn update_tab(siv: &mut Cursive, tab: &mut Tab) { 345 | // let focused = if !forward { tab.p_focused } else { 346 | // if let Some(c) = tab.c_focused {c} 347 | // else {0} 348 | // }; 349 | siv.call_on_id( 350 | "current", 351 | |event_view: &mut OnEventView>| { 352 | let c_focused = tab.focused.get(&tab.c_view).unwrap_or(&0usize); 353 | debug!("Got current selection of: {:?}", c_focused); 354 | let view = event_view.get_inner_mut(); 355 | view.clear(); 356 | for entry in App::get_path_iter(&tab.c_view) 357 | .filter_entry(|e| e.path().is_dir() && !filter::is_hidden(e)) 358 | { 359 | let entry = entry.unwrap(); 360 | match entry.file_name().to_str() { 361 | Some(c) => { 362 | view.add_item(format!(r"  {}", c), PathBuf::from(entry.path())) 363 | } 364 | None => {} 365 | } 366 | } 367 | for entry in App::get_path_iter(&tab.c_view) 368 | .filter_entry(|e| e.path().is_file() && !filter::is_hidden(e)) 369 | { 370 | let entry = entry.unwrap(); 371 | match entry.file_name().to_str() { 372 | Some(c) => { 373 | view.add_item(format!(r"  {}", c), PathBuf::from(entry.path())) 374 | } 375 | None => {} 376 | }; 377 | } 378 | // TODO keep last selection 379 | view.set_selection(*c_focused); 380 | //view.set_selection(focused); 381 | }, 382 | ); 383 | 384 | let mut i: usize = 0; 385 | siv.call_on_id("parent", |view: &mut MultiSelectView| { 386 | view.clear(); 387 | match tab.p_view.to_str() { 388 | Some("root") => { 389 | view.add_item("/", PathBuf::from("/")); 390 | view.set_selection(0); 391 | } 392 | Some(_) | None => { 393 | for (index, entry) in App::get_path_iter(&tab.p_view) 394 | .filter_entry(|e| e.path().is_dir() && !filter::is_hidden(e)) 395 | .enumerate() 396 | { 397 | let entry = entry.unwrap(); 398 | if entry.path() == &tab.c_view { 399 | i = index; 400 | } 401 | match entry.file_name().to_str() { 402 | Some(c) => { 403 | view.add_item(format!("  {}", c), PathBuf::from(entry.path())) 404 | } 405 | None => {} 406 | }; 407 | } 408 | for entry in App::get_path_iter(&tab.p_view) 409 | .filter_entry(|e| e.path().is_file() && !filter::is_hidden(e)) 410 | { 411 | let entry = entry.unwrap(); 412 | match entry.file_name().to_str() { 413 | Some(c) => { 414 | view.add_item(format!("  {}", c), PathBuf::from(entry.path())) 415 | } 416 | None => {} 417 | }; 418 | } 419 | view.set_selection(i); 420 | } 421 | } 422 | }); 423 | // tab.p_focused = i; 424 | tab.focused.insert(PathBuf::from(&tab.p_view), i); 425 | debug!("Updated focused for parent: {:?}", tab); 426 | } 427 | 428 | fn get_path_iter(path: &PathBuf) -> walkdir::IntoIter { 429 | WalkDir::new(path) 430 | .max_depth(1) 431 | .min_depth(1) 432 | .sort_by(|a, b| compare_os_str(&a.file_name(), &b.file_name())) 433 | .into_iter() 434 | } 435 | 436 | #[allow(dead_code)] 437 | fn add_layout(&mut self) { 438 | // something 439 | } 440 | 441 | /// Funtion to handle the event loop. 442 | /// 443 | /// Currently does a naive call to `siv.run()` 444 | pub fn run(&mut self) { 445 | self.siv.run(); 446 | } 447 | } 448 | 449 | /// Funtion to update status bar content and preview widget content on selection change. 450 | /// First the status bar is updated to show relevant permission details and size of file. 451 | /// Then preview is updated to reflect details about the selected entry. 452 | fn update_info(siv: &mut Cursive, entry: &PathBuf) { 453 | siv.call_on_id("preview", |view: &mut TextView| { 454 | if !entry.is_dir() { 455 | let data: Mime = guess_mime_type(entry); 456 | view.set_content(format!("{}/{}", data.type_(), data.subtype())) 457 | } else { 458 | view.set_content("This is a directory!".to_string()) 459 | } 460 | }); 461 | siv.call_on_id("status", |view: &mut TextView| { 462 | view.set_content( 463 | Entry::from(PathBuf::from(entry)) 464 | .permission_string() 465 | .unwrap(), 466 | ); 467 | }); 468 | } 469 | 470 | fn create_console(prefix: &str) -> LinearLayout { 471 | let prefix_view = TextView::new(prefix); 472 | let edit_view = EditView::new().filler(" ") 473 | .on_submit(|s, content| { 474 | debug!("You entered {}", content); 475 | s.pop_layer(); 476 | }) 477 | .style(ColorStyle::new(ColorType::from(PaletteColor::Background), 478 | ColorType::from(PaletteColor::Primary))).with_id("console"); 479 | let layout = LinearLayout::horizontal() 480 | .child(prefix_view) 481 | .child(edit_view.full_width()); 482 | layout 483 | } 484 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | //! Core module of the app 2 | pub mod app; 3 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error handling for marcos file manager 2 | 3 | use std::io; 4 | use std::{fmt, result}; 5 | 6 | use log::SetLoggerError; 7 | use toml::de; 8 | 9 | use failure; 10 | use failure::{Backtrace, Context, Fail}; 11 | 12 | pub type Result = result::Result; 13 | 14 | #[derive(Debug)] 15 | pub struct Error { 16 | inner: Context, 17 | } 18 | 19 | impl Fail for Error { 20 | fn cause(&self) -> Option<&Fail> { 21 | self.inner.cause() 22 | } 23 | 24 | fn backtrace(&self) -> Option<&Backtrace> { 25 | self.inner.backtrace() 26 | } 27 | } 28 | 29 | impl fmt::Display for Error { 30 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 31 | fmt::Display::fmt(&self.inner, f) 32 | } 33 | } 34 | 35 | /// Kinds of error which need to be handled 36 | #[derive(Debug, Fail)] 37 | pub enum ErrorKind { 38 | #[fail(display = "IO Error")] 39 | Io(#[cause] io::Error), 40 | 41 | #[fail(display = "Directory not found: {}", dirname)] 42 | DirNotFound { dirname: String }, 43 | 44 | #[fail(display = "Error while initializing logger!")] 45 | LogInitError(#[cause] SetLoggerError), 46 | // TODO handle generic error 47 | #[fail(display = "Toml deserialization error")] 48 | TomlDeError(#[cause] de::Error), 49 | 50 | #[fail(display = "Generic Error")] 51 | GenericError, 52 | } 53 | 54 | impl From for Error { 55 | fn from(kind: ErrorKind) -> Error { 56 | Error { 57 | inner: Context::new(kind), 58 | } 59 | } 60 | } 61 | 62 | impl From> for Error { 63 | fn from(kind: Context) -> Error { 64 | Error { inner: kind } 65 | } 66 | } 67 | 68 | impl From for Error { 69 | fn from(kind: io::Error) -> Error { 70 | Error { 71 | inner: Context::new(ErrorKind::Io(kind)), 72 | } 73 | } 74 | } 75 | 76 | impl From for Error { 77 | fn from(kind: SetLoggerError) -> Error { 78 | Error { 79 | inner: Context::new(ErrorKind::LogInitError(kind)), 80 | } 81 | } 82 | } 83 | 84 | impl From for Error { 85 | fn from(kind: de::Error) -> Error { 86 | Error { 87 | inner: Context::new(ErrorKind::TomlDeError(kind)), 88 | } 89 | } 90 | } 91 | 92 | /// Return a prettily formatted error, including its entire causal chain. 93 | pub fn failure_to_string(err: &failure::Error) -> String { 94 | let mut pretty = err.to_string(); 95 | let mut prev = err.as_fail(); 96 | while let Some(next) = prev.cause() { 97 | pretty.push_str(": "); 98 | pretty.push_str(&next.to_string()); 99 | prev = next; 100 | } 101 | pretty 102 | } 103 | -------------------------------------------------------------------------------- /src/fs/metadata.rs: -------------------------------------------------------------------------------- 1 | //! This module contains code to retrieve metadata about file/directory such as permissions, 2 | //! owners, size, etc. 3 | use std::os::unix::fs::*; 4 | use std::path::PathBuf; 5 | 6 | use users::{get_group_by_gid, get_user_by_uid}; 7 | 8 | use crate::error::*; 9 | 10 | mod modes { 11 | pub type Mode = u32; 12 | 13 | pub const USER_READ: Mode = 256; 14 | pub const USER_WRITE: Mode = 128; 15 | pub const USER_EXECUTE: Mode = 64; 16 | 17 | pub const GROUP_READ: Mode = 32; 18 | pub const GROUP_WRITE: Mode = 16; 19 | pub const GROUP_EXECUTE: Mode = 8; 20 | 21 | pub const OTHER_READ: Mode = 4; 22 | pub const OTHER_WRITE: Mode = 2; 23 | pub const OTHER_EXECUTE: Mode = 1; 24 | } 25 | 26 | struct Permissions { 27 | pub user_read: bool, 28 | pub user_write: bool, 29 | pub user_execute: bool, 30 | 31 | pub group_read: bool, 32 | pub group_write: bool, 33 | pub group_execute: bool, 34 | 35 | pub other_read: bool, 36 | pub other_write: bool, 37 | pub other_execute: bool, 38 | // pub sticky: bool, 39 | // pub setgid: bool, 40 | // pub setuid: bool, 41 | } 42 | 43 | impl From for Permissions { 44 | fn from(bits: u32) -> Self { 45 | let has_bit = |bit| bits & bit == bit; 46 | Self { 47 | user_read: has_bit(modes::USER_READ), 48 | user_write: has_bit(modes::USER_WRITE), 49 | user_execute: has_bit(modes::USER_EXECUTE), 50 | 51 | group_read: has_bit(modes::GROUP_READ), 52 | group_write: has_bit(modes::GROUP_WRITE), 53 | group_execute: has_bit(modes::GROUP_EXECUTE), 54 | 55 | other_read: has_bit(modes::OTHER_READ), 56 | other_write: has_bit(modes::OTHER_WRITE), 57 | other_execute: has_bit(modes::OTHER_EXECUTE), 58 | // sticky: has_bit(modes::STICKY), 59 | // setgid: has_bit(modes::SETGID), 60 | // setuid: has_bit(modes::SETUID), 61 | } 62 | } 63 | } 64 | 65 | impl ToString for Permissions { 66 | fn to_string(&self) -> String { 67 | let mut repr = String::with_capacity(9); 68 | if self.user_read { 69 | repr.push('r') 70 | } else { 71 | repr.push('-') 72 | } 73 | if self.user_write { 74 | repr.push('w') 75 | } else { 76 | repr.push('-') 77 | } 78 | if self.user_execute { 79 | repr.push('x') 80 | } else { 81 | repr.push('-') 82 | } 83 | 84 | if self.group_read { 85 | repr.push('r') 86 | } else { 87 | repr.push('-') 88 | } 89 | if self.group_write { 90 | repr.push('w') 91 | } else { 92 | repr.push('-') 93 | } 94 | if self.group_execute { 95 | repr.push('x') 96 | } else { 97 | repr.push('-') 98 | } 99 | 100 | if self.other_read { 101 | repr.push('r') 102 | } else { 103 | repr.push('-') 104 | } 105 | if self.other_write { 106 | repr.push('w') 107 | } else { 108 | repr.push('-') 109 | } 110 | if self.other_execute { 111 | repr.push('x') 112 | } else { 113 | repr.push('-') 114 | } 115 | repr 116 | } 117 | } 118 | 119 | /// Represents an entry. It can be a file or a directory. 120 | /// Contains information such as number of files(if its a directory), permissions, 121 | /// groups, owners, size, etc. 122 | pub struct Entry { 123 | path: PathBuf, 124 | } 125 | 126 | /// Initialize from PathBuf 127 | impl From for Entry { 128 | fn from(path: PathBuf) -> Self { 129 | Self { path } 130 | } 131 | } 132 | 133 | impl Entry { 134 | /// Funtion which returns String representing permissions and owner of selected entry. 135 | pub fn permission_string(&self) -> Result { 136 | let meta = self.path.metadata()?; 137 | let uid = meta.uid(); 138 | let gid = meta.gid(); 139 | let uid = get_user_by_uid(uid).unwrap(); 140 | let gid = get_group_by_gid(gid).unwrap(); 141 | let mut repr = String::with_capacity(10); 142 | if self.path.is_dir() { 143 | repr.push('d') 144 | } else { 145 | repr.push('-') 146 | } 147 | Ok(repr 148 | + &Permissions::from(meta.mode()).to_string() 149 | + &format!(" {}:{}", uid.name().to_str().unwrap(), gid.name().to_str().unwrap())) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/fs/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains structs and functions related to file IO. 2 | pub use self::metadata::Entry; 3 | pub mod metadata; 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Marcos is a command-line file manager in Rust with VIM-inspired key-bindings 2 | //! Currently, it is a very early stage of development. Only Unix-like platforms are supported. 3 | //! 4 | //! # Install 5 | //! Marcos requires the [Rust](https://www.rust-lang.org/en-US/) programming language installed on your system. Also, make sure `ncurses` is installed on your system. 6 | //! 7 | //! Fire up a terminal and run _one_ of the following: 8 | //! ```bash 9 | //! git clone https://github.com/TerminalWitchcraft/marcos && cd marcos 10 | //! cargo build --release 11 | //! sudo mv ./target/release/mcf /usr/bin 12 | //! ``` 13 | //! or 14 | //! ```bash 15 | //! cargo install --git https://github.com/TerminalWitchcraft/marcos 16 | //! ``` 17 | //! 18 | //! # Configuration 19 | //! Marcos is configured via a set of [toml](https://github.com/toml-lang/toml) files located at 20 | //! `$XDG_CONFIG_HOME/marcos` or `~/.config/marcos`. 21 | //! 22 | //! Also run `mcf --help` to list out all possible options. 23 | //! 24 | //! # Key bindings 25 | //! 26 | //! Below is the list of default key bindings: 27 | //! 28 | //! | Key | Action | 29 | //! |----------|---------------------------------------------------------------------------------------| 30 | //! | q | Exit marcos | 31 | //! | j | Select item down | 32 | //! | k | Select item up | 33 | //! | h | Go previous (left) | 34 | //! | l | Go next(right) | 35 | //! | : | Activate command mode | 36 | //! | gg | Go to the first selection | 37 | //! | G | Go to the last selection | 38 | //! | [count]G | Go to the [count] item | 39 | //! | za | Toggle visibility of hidden items | 40 | //! | y | Yank(Copy) the selected file/folder(Similar to Ctrl-c) | 41 | //! | x | Cut the selected file/folder(similar to Ctrl-x) | 42 | //! | p | Paste the Copied/Cut file/folder(Similar to Ctrl-v) | 43 | //! | r | Rename selected file/folder | 44 | //! | dd | Delete selected file/folder(with confirmation) | 45 | //! | o | Create new file(`touch filename`) | 46 | //! | O | Create new directory (`mkdir dirname`) | 47 | //! | P | Paste the Copied/Cut file/folder replacing existing with same name(with Confirmation) | 48 | //! | mX | Create a bookmark with name X | 49 | //! | `X | Jump to bookmark with name X | 50 | //! | n | Move to next match | 51 | //! | N | Move to previous match | 52 | //! | / | Search | 53 | //! | v | Starts visual mode, selects all files until you press ESC | 54 | //! | V | Visual mode, select all | 55 | //! | Ctrl+r | Refresh(listings, data, cache, etc) | 56 | //! | ESC | Get me out! | 57 | //! | | | 58 | 59 | #[macro_use] 60 | extern crate log; 61 | extern crate alphanumeric_sort; 62 | extern crate cursive; 63 | extern crate dirs; 64 | extern crate failure; 65 | extern crate fern; 66 | extern crate mime_guess; 67 | extern crate systemstat; 68 | extern crate uname; 69 | extern crate users; 70 | extern crate walkdir; 71 | #[macro_use] 72 | extern crate failure_derive; 73 | extern crate serde; 74 | extern crate toml; 75 | extern crate unicode_width; 76 | #[macro_use] 77 | extern crate serde_derive; 78 | 79 | pub mod config; 80 | pub mod core; 81 | pub mod error; 82 | pub mod fs; 83 | pub mod ui; 84 | pub mod utils; 85 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate cursive; 3 | extern crate failure; 4 | extern crate marcos; 5 | 6 | use clap::{App, Arg}; 7 | 8 | use marcos::core; 9 | use marcos::error; 10 | 11 | fn main() { 12 | if let Err(err) = try_main() { 13 | println!("{}", error::failure_to_string(&err)); 14 | let backtrace = err.backtrace().to_string(); 15 | if !backtrace.trim().is_empty() { 16 | eprintln!("{}", backtrace); 17 | } 18 | ::std::process::exit(1); 19 | } 20 | } 21 | 22 | fn try_main() -> Result<(), failure::Error> { 23 | let matches = App::new("Marcos") 24 | .version("0.1.0") 25 | .author("Hitesh Paul") 26 | .about("Command line file-manager inspired by vim-like keybinding") 27 | .arg( 28 | Arg::with_name("path") 29 | .required(false) 30 | .help("Path to a directory where marcos should open") 31 | .value_name("PATH"), 32 | ).arg( 33 | Arg::with_name("log") 34 | .required(false) 35 | .help("Path to dump log file") 36 | .short("l") 37 | .long("log") 38 | .value_name("LOG"), 39 | ).arg( 40 | Arg::with_name("log_level") 41 | .requires("log") 42 | .help("Level of log files") 43 | .short("v") 44 | .long("level") 45 | .possible_values(&["debug", "info", "error"]) 46 | .value_name("LOG_LEVEL"), 47 | ).get_matches(); 48 | let mut app = core::app::init( 49 | match matches.value_of("path") { 50 | Some(c) => c, 51 | None => ".", 52 | }, 53 | matches.value_of("log"), 54 | matches.value_of("log_level"), 55 | ).unwrap(); 56 | app.run(); 57 | // if let Some(c) = matches.value_of("path") { 58 | // let mut app = core::app::init(c).unwrap(); 59 | // // println!("Got {} for path", c); 60 | // } else { 61 | // println!("Wow"); 62 | // } 63 | // let mut app = core::app::init().unwrap(); 64 | // app.run(); 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /src/ui/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains structs and function to manipulate view of the file manager. 2 | pub mod multi_select; 3 | pub mod tab; 4 | pub use self::multi_select::MultiSelectView; 5 | pub use crate::ui::tab::Tab; 6 | -------------------------------------------------------------------------------- /src/ui/multi_select.rs: -------------------------------------------------------------------------------- 1 | use cursive::align::{Align, HAlign, VAlign}; 2 | use cursive::direction::Direction; 3 | use cursive::event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent}; 4 | use cursive::menu::MenuTree; 5 | use cursive::rect::Rect; 6 | use cursive::theme::ColorStyle; 7 | use cursive::vec::Vec2; 8 | use cursive::view::{Position, View}; 9 | use cursive::views::MenuPopup; 10 | use cursive::Cursive; 11 | use cursive::Printer; 12 | use cursive::With; 13 | use std::borrow::Borrow; 14 | use std::cell::Cell; 15 | use std::cmp::min; 16 | use std::rc::Rc; 17 | use unicode_width::UnicodeWidthStr; 18 | 19 | /// View to select an item among a list. 20 | /// 21 | /// It contains a list of values of type T, with associated labels. 22 | /// 23 | /// # Examples 24 | /// 25 | /// ```rust 26 | /// # extern crate cursive; 27 | /// # extern crate marcos; 28 | /// # use cursive::Cursive; 29 | /// # use cursive::views::{Dialog, TextView}; 30 | /// # use cursive::align::HAlign; 31 | /// # use marcos::ui::multi_select::MultiSelectView; 32 | /// # fn main() { 33 | /// let mut time_select = MultiSelectView::new().h_align(HAlign::Center); 34 | /// time_select.add_item("Short", 1); 35 | /// time_select.add_item("Medium", 5); 36 | /// time_select.add_item("Long", 10); 37 | /// 38 | /// time_select.set_on_submit(|s, time| { 39 | /// s.pop_layer(); 40 | /// let text = format!("You will wait for {} minutes...", time); 41 | /// s.add_layer(Dialog::around(TextView::new(text)) 42 | /// .button("Quit", |s| s.quit())); 43 | /// }); 44 | /// 45 | /// let mut siv = Cursive::dummy(); 46 | /// siv.add_layer(Dialog::around(time_select) 47 | /// .title("How long is your wait?")); 48 | /// # } 49 | /// 50 | /// ``` 51 | pub struct MultiSelectView { 52 | items: Vec>, 53 | enabled: bool, 54 | // the focus needs to be manipulable from callbacks 55 | focus: Rc>, 56 | // This is a custom callback to include a &T. 57 | // It will be called whenever "Enter" is pressed. 58 | on_submit: Option>, 59 | // This callback is called when the selection is changed. 60 | on_select: Option>, 61 | align: Align, 62 | // `true` if we show a one-line view, with popup on selection. 63 | popup: bool, 64 | // We need the last offset to place the popup window 65 | // We "cache" it during the draw, so we need interior mutability. 66 | last_offset: Cell, 67 | last_size: Vec2, 68 | input_buffer: Vec, 69 | input_num_buffer: Vec, 70 | input_count: usize, 71 | } 72 | 73 | impl Default for MultiSelectView { 74 | fn default() -> Self { 75 | Self::new() 76 | } 77 | } 78 | 79 | impl MultiSelectView { 80 | /// Creates a new empty SelectView. 81 | pub fn new() -> Self { 82 | MultiSelectView { 83 | items: Vec::new(), 84 | enabled: true, 85 | focus: Rc::new(Cell::new(0)), 86 | on_select: None, 87 | on_submit: None, 88 | align: Align::top_left(), 89 | popup: false, 90 | last_offset: Cell::new(Vec2::zero()), 91 | last_size: Vec2::zero(), 92 | input_buffer: Vec::new(), 93 | input_num_buffer: Vec::new(), 94 | input_count: 0, 95 | } 96 | } 97 | 98 | /// Turns `self` into a popup select view. 99 | /// 100 | /// Chainable variant. 101 | pub fn popup(self) -> Self { 102 | self.with(|s| s.set_popup(true)) 103 | } 104 | 105 | /// Turns `self` into a popup select view. 106 | pub fn set_popup(&mut self, popup: bool) { 107 | self.popup = popup; 108 | } 109 | 110 | /// Disables this view. 111 | /// 112 | /// A disabled view cannot be selected. 113 | pub fn disable(&mut self) { 114 | self.enabled = false; 115 | } 116 | 117 | /// Disables this view. 118 | /// 119 | /// Chainable variant. 120 | pub fn disabled(self) -> Self { 121 | self.with(Self::disable) 122 | } 123 | 124 | /// Re-enables this view. 125 | pub fn enable(&mut self) { 126 | self.enabled = true; 127 | } 128 | 129 | /// Enable or disable this view. 130 | pub fn set_enabled(&mut self, enabled: bool) { 131 | self.enabled = enabled; 132 | } 133 | 134 | /// Returns `true` if this view is enabled. 135 | pub fn is_enabled(&self) -> bool { 136 | self.enabled 137 | } 138 | 139 | /// Sets a callback to be used when an item is selected. 140 | pub fn set_on_select(&mut self, cb: F) 141 | where 142 | F: Fn(&mut Cursive, &T) + 'static, 143 | { 144 | self.on_select = Some(Rc::new(cb)); 145 | } 146 | 147 | /// Sets a callback to be used when an item is selected. 148 | /// 149 | /// Chainable variant. 150 | pub fn on_select(self, cb: F) -> Self 151 | where 152 | F: Fn(&mut Cursive, &T) + 'static, 153 | { 154 | self.with(|s| s.set_on_select(cb)) 155 | } 156 | 157 | /// Sets a callback to be used when `` is pressed. 158 | /// 159 | /// The item currently selected will be given to the callback. 160 | /// 161 | /// Here, `V` can be `T` itself, or a type that can be borrowed from `T`. 162 | pub fn set_on_submit(&mut self, cb: F) 163 | where 164 | F: 'static + Fn(&mut Cursive, &V) -> R, 165 | T: Borrow, 166 | { 167 | self.on_submit = Some(Rc::new(move |s, t| { 168 | cb(s, t.borrow()); 169 | })); 170 | } 171 | 172 | /// Sets a callback to be used when `` is pressed. 173 | /// 174 | /// The item currently selected will be given to the callback. 175 | /// 176 | /// Chainable variant. 177 | pub fn on_submit(self, cb: F) -> Self 178 | where 179 | F: Fn(&mut Cursive, &V) + 'static, 180 | T: Borrow, 181 | { 182 | self.with(|s| s.set_on_submit(cb)) 183 | } 184 | 185 | /// Sets the alignment for this view. 186 | pub fn align(mut self, align: Align) -> Self { 187 | self.align = align; 188 | 189 | self 190 | } 191 | 192 | /// Sets the vertical alignment for this view. 193 | /// (If the view is given too much space vertically.) 194 | pub fn v_align(mut self, v: VAlign) -> Self { 195 | self.align.v = v; 196 | 197 | self 198 | } 199 | 200 | /// Sets the horizontal alignment for this view. 201 | pub fn h_align(mut self, h: HAlign) -> Self { 202 | self.align.h = h; 203 | 204 | self 205 | } 206 | 207 | /// Returns the value of the currently selected item. 208 | /// 209 | /// Returns `None` if the list is empty. 210 | pub fn selection(&self) -> Option> { 211 | let focus = self.focus(); 212 | if self.len() <= focus { 213 | None 214 | } else { 215 | Some(Rc::clone(&self.items[focus].value)) 216 | } 217 | } 218 | 219 | /// Removes all items from this view. 220 | pub fn clear(&mut self) { 221 | self.items.clear(); 222 | self.focus.set(0); 223 | } 224 | 225 | /// Adds a item to the list, with given label and value. 226 | pub fn add_item>(&mut self, label: S, value: T) { 227 | self.items.push(Item::new(label.into(), value)); 228 | } 229 | 230 | /// Gets an item at given idx or None. 231 | /// 232 | /// ``` 233 | /// extern crate cursive; 234 | /// extern crate marcos; 235 | /// use cursive::Cursive; 236 | /// use cursive::views::{TextView}; 237 | /// use marcos::ui::multi_select::MultiSelectView; 238 | /// let select = MultiSelectView::new() 239 | /// .item("Short", 1); 240 | /// assert_eq!(select.get_item(0), Some(("Short", &1))); 241 | /// ``` 242 | pub fn get_item(&self, i: usize) -> Option<(&str, &T)> { 243 | self.items 244 | .get(i) 245 | .map(|item| (item.label.as_ref(), &*item.value)) 246 | } 247 | 248 | /// Gets a mut item at given idx or None. 249 | pub fn get_item_mut(&mut self, i: usize) -> Option<(&mut String, &mut T)> { 250 | if i >= self.items.len() { 251 | None 252 | } else { 253 | let item = &mut self.items[i]; 254 | if let Some(t) = Rc::get_mut(&mut item.value) { 255 | let label = &mut item.label; 256 | Some((label, t)) 257 | } else { 258 | None 259 | } 260 | } 261 | } 262 | 263 | /// Removes an item from the list. 264 | /// 265 | /// Returns a callback in response to the selection change. 266 | /// 267 | /// You should run this callback with a `&mut Cursive`. 268 | pub fn remove_item(&mut self, id: usize) -> Callback { 269 | self.items.remove(id); 270 | let focus = self.focus(); 271 | if focus >= id && focus > 0 { 272 | self.focus.set(focus - 1); 273 | } 274 | 275 | self.make_select_cb().unwrap_or_else(Callback::dummy) 276 | } 277 | 278 | /// Inserts an item at position `index`, shifting all elements after it to 279 | /// the right. 280 | pub fn insert_item(&mut self, index: usize, label: S, value: T) 281 | where 282 | S: Into, 283 | { 284 | self.items.insert(index, Item::new(label.into(), value)); 285 | } 286 | 287 | /// Chainable variant of add_item 288 | pub fn item>(self, label: S, value: T) -> Self { 289 | self.with(|s| s.add_item(label, value)) 290 | } 291 | 292 | /// Adds all items from from an iterator. 293 | pub fn add_all(&mut self, iter: I) 294 | where 295 | S: Into, 296 | I: IntoIterator, 297 | { 298 | for (s, t) in iter { 299 | self.add_item(s, t); 300 | } 301 | } 302 | 303 | /// Adds all items from from an iterator. 304 | /// 305 | /// Chainable variant. 306 | pub fn with_all(self, iter: I) -> Self 307 | where 308 | S: Into, 309 | I: IntoIterator, 310 | { 311 | self.with(|s| s.add_all(iter)) 312 | } 313 | 314 | fn draw_item(&self, printer: &Printer, i: usize) { 315 | let l = self.items[i].label.width(); 316 | let x = self.align.h.get_offset(l, printer.size.x); 317 | printer.print_hline((0, 0), x, " "); 318 | printer.print((x, 0), &self.items[i].label); 319 | if l < printer.size.x { 320 | assert!((l + x) <= printer.size.x); 321 | printer.print_hline((x + l, 0), printer.size.x - (l + x), " "); 322 | } 323 | } 324 | 325 | /// Returns the id of the item currently selected. 326 | /// 327 | /// Returns `None` if the list is empty. 328 | pub fn selected_id(&self) -> Option { 329 | if self.items.is_empty() { 330 | None 331 | } else { 332 | Some(self.focus()) 333 | } 334 | } 335 | 336 | /// Returns the number of items in this list. 337 | pub fn len(&self) -> usize { 338 | self.items.len() 339 | } 340 | 341 | /// Returns `true` if this list has no item. 342 | pub fn is_empty(&self) -> bool { 343 | self.items.is_empty() 344 | } 345 | 346 | fn focus(&self) -> usize { 347 | self.focus.get() 348 | } 349 | 350 | /// Moves the selection to the given position. 351 | /// 352 | /// Returns a callback in response to the selection change. 353 | /// 354 | /// You should run this callback with a `&mut Cursive`. 355 | pub fn set_selection(&mut self, i: usize) -> Callback { 356 | // TODO: Check if `i >= self.len()` ? 357 | // assert!(i < self.len(), "SelectView: trying to select out-of-bound"); 358 | // Or just cap the ID? 359 | let i = if self.is_empty() { 360 | 0 361 | } else { 362 | min(i, self.len() - 1) 363 | }; 364 | self.focus.set(i); 365 | 366 | self.make_select_cb().unwrap_or_else(Callback::dummy) 367 | } 368 | 369 | /// Sets the selection to the given position. 370 | /// 371 | /// Chainable variant. 372 | /// 373 | /// Does not apply `on_select` callbacks. 374 | pub fn selected(self, i: usize) -> Self { 375 | self.with(|s| { 376 | s.set_selection(i); 377 | }) 378 | } 379 | 380 | /// Moves the selection up by the given number of rows. 381 | /// 382 | /// Returns a callback in response to the selection change. 383 | /// 384 | /// You should run this callback with a `&mut Cursive`: 385 | /// 386 | /// ```rust 387 | /// # extern crate cursive; 388 | /// # extern crate marcos; 389 | /// # use cursive::Cursive; 390 | /// # use marcos::ui::multi_select::MultiSelectView; 391 | /// # fn main() {} 392 | /// fn select_up(siv: &mut Cursive, view: &mut MultiSelectView<()>) { 393 | /// let cb = view.select_up(1); 394 | /// cb(siv); 395 | /// } 396 | /// ``` 397 | pub fn select_up(&mut self, n: usize) -> Callback { 398 | self.focus_up(n); 399 | self.make_select_cb().unwrap_or_else(Callback::dummy) 400 | } 401 | 402 | /// Moves the selection down by the given number of rows. 403 | /// 404 | /// Returns a callback in response to the selection change. 405 | /// 406 | /// You should run this callback with a `&mut Cursive`. 407 | pub fn select_down(&mut self, n: usize) -> Callback { 408 | self.focus_down(n); 409 | self.make_select_cb().unwrap_or_else(Callback::dummy) 410 | } 411 | 412 | fn focus_up(&mut self, n: usize) { 413 | let focus = self.focus().saturating_sub(n); 414 | self.focus.set(focus); 415 | } 416 | 417 | fn focus_down(&mut self, n: usize) { 418 | let focus = min(self.focus() + n, self.items.len().saturating_sub(1)); 419 | self.focus.set(focus); 420 | } 421 | 422 | fn submit(&mut self) -> EventResult { 423 | let cb = self.on_submit.clone().unwrap(); 424 | // We return a Callback Rc<|s| cb(s, &*v)> 425 | EventResult::Consumed( 426 | self.selection() 427 | .map(|v| Callback::from_fn(move |s| cb(s, &v))), 428 | ) 429 | } 430 | 431 | fn on_event_regular(&mut self, event: Event) -> EventResult { 432 | if is_numeric(&event) { 433 | let number: Option = match event { 434 | Event::Char(c) => c.to_digit(10), 435 | _ => None, 436 | }; 437 | if let Some(c) = number { 438 | self.input_num_buffer.push(c as usize) 439 | }; 440 | return EventResult::Ignored; 441 | } 442 | 443 | if should_intercept(&event) { 444 | self.input_buffer.push(event.clone()) 445 | } 446 | match self.input_buffer.as_slice() { 447 | [Event::Char('g'), Event::Char('g')] => { 448 | self.focus.set(0); 449 | self.input_buffer.clear(); 450 | return EventResult::Consumed(self.make_select_cb()); 451 | } 452 | [Event::Char('G')] => { 453 | let num = get_number(&self.input_num_buffer); 454 | if num == 0 { 455 | } else { 456 | if num - 1 < self.items.len() { 457 | self.focus.set(num - 1); 458 | } else { 459 | self.focus.set(self.items.len().saturating_sub(1)); 460 | } 461 | self.input_buffer.clear(); 462 | self.input_num_buffer.clear(); 463 | return EventResult::Consumed(self.make_select_cb()); 464 | } 465 | } 466 | _ => {} 467 | } 468 | match event { 469 | Event::Key(Key::Esc) => self.input_num_buffer.clear(), 470 | Event::Key(Key::Up) if self.focus() > 0 => self.focus_up(1), 471 | Event::Key(Key::Down) if self.focus() + 1 < self.items.len() => self.focus_down(1), 472 | Event::Key(Key::PageUp) => self.focus_up(10), 473 | Event::Key(Key::PageDown) => self.focus_down(10), 474 | Event::Key(Key::Home) => self.focus.set(0), 475 | Event::Key(Key::End) => self.focus.set(self.items.len().saturating_sub(1)), 476 | Event::Char('G') => self.focus.set(self.items.len().saturating_sub(1)), 477 | // Event::Char('/') => { 478 | // debug!("You pressed search key!"); 479 | // }, 480 | Event::Mouse { 481 | event: MouseEvent::Press(_), 482 | position, 483 | offset, 484 | } 485 | if position 486 | .checked_sub(offset) 487 | .map(|position| position < self.last_size && position.y < self.len()) 488 | .unwrap_or(false) => 489 | { 490 | self.focus.set(position.y - offset.y) 491 | } 492 | Event::Mouse { 493 | event: MouseEvent::Release(MouseButton::Left), 494 | position, 495 | offset, 496 | } 497 | if self.on_submit.is_some() && position 498 | .checked_sub(offset) 499 | .map(|position| position < self.last_size && position.y == self.focus()) 500 | .unwrap_or(false) => 501 | { 502 | return self.submit(); 503 | } 504 | Event::Key(Key::Enter) if self.on_submit.is_some() => { 505 | return self.submit(); 506 | } 507 | // Event::Char(c) => { 508 | // // Starting from the current focus, 509 | // // find the first item that match the char. 510 | // // Cycle back to the beginning of 511 | // // the list when we reach the end. 512 | // // This is achieved by chaining twice the iterator 513 | // let iter = self.items.iter().chain(self.items.iter()); 514 | // if let Some((i, _)) = iter 515 | // .enumerate() 516 | // .skip(self.focus() + 1) 517 | // .find(|&(_, item)| item.label.starts_with(c)) 518 | // { 519 | // // Apply modulo in case we have a hit 520 | // // from the chained iterator 521 | // self.focus.set(i % self.items.len()); 522 | // } else { 523 | // return EventResult::Ignored; 524 | // } 525 | // } 526 | _ => return EventResult::Ignored, 527 | } 528 | 529 | EventResult::Consumed(self.make_select_cb()) 530 | } 531 | 532 | /// Returns a callback from selection change. 533 | fn make_select_cb(&self) -> Option { 534 | self.on_select.clone().and_then(|cb| { 535 | self.selection() 536 | .map(|v| Callback::from_fn(move |s| cb(s, &v))) 537 | }) 538 | } 539 | 540 | fn open_popup(&mut self) -> EventResult { 541 | // Build a shallow menu tree to mimick the items array. 542 | // TODO: cache it? 543 | let mut tree = MenuTree::new(); 544 | for (i, item) in self.items.iter().enumerate() { 545 | let focus = Rc::clone(&self.focus); 546 | let on_submit = self.on_submit.as_ref().cloned(); 547 | let value = Rc::clone(&item.value); 548 | tree.add_leaf(item.label.clone(), move |s| { 549 | // TODO: What if an item was removed in the meantime? 550 | focus.set(i); 551 | if let Some(ref on_submit) = on_submit { 552 | on_submit(s, &value); 553 | } 554 | }); 555 | } 556 | // Let's keep the tree around, 557 | // the callback will want to use it. 558 | let tree = Rc::new(tree); 559 | 560 | let focus = self.focus(); 561 | // This is the offset for the label text. 562 | // We'll want to show the popup so that the text matches. 563 | // It'll be soo cool. 564 | let item_length = self.items[focus].label.len(); 565 | let text_offset = (self.last_size.x.saturating_sub(item_length)) / 2; 566 | // The total offset for the window is: 567 | // * the last absolute offset at which we drew this view 568 | // * shifted to the right of the text offset 569 | // * shifted to the top of the focus (so the line matches) 570 | // * shifted top-left of the border+padding of the popup 571 | let offset = self.last_offset.get(); 572 | let offset = offset + (text_offset, 0); 573 | let offset = offset.saturating_sub((0, focus)); 574 | let offset = offset.saturating_sub((2, 1)); 575 | 576 | // And now, we can return the callback that will create the popup. 577 | EventResult::with_cb(move |s| { 578 | // The callback will want to work with a fresh Rc 579 | let tree = Rc::clone(&tree); 580 | // We'll relativise the absolute position, 581 | // So that we are locked to the parent view. 582 | // A nice effect is that window resizes will keep both 583 | // layers together. 584 | let current_offset = s.screen().offset(); 585 | let offset = offset.signed() - current_offset; 586 | // And finally, put the view in view! 587 | s.screen_mut() 588 | .add_layer_at(Position::parent(offset), MenuPopup::new(tree).focus(focus)); 589 | }) 590 | } 591 | 592 | // A popup view only does one thing: open the popup on Enter. 593 | fn on_event_popup(&mut self, event: Event) -> EventResult { 594 | match event { 595 | // TODO: add Left/Right support for quick-switch? 596 | Event::Key(Key::Enter) => self.open_popup(), 597 | Event::Mouse { 598 | event: MouseEvent::Release(MouseButton::Left), 599 | position, 600 | offset, 601 | } 602 | if position.fits_in_rect(offset, self.last_size) => 603 | { 604 | self.open_popup() 605 | } 606 | _ => EventResult::Ignored, 607 | } 608 | } 609 | } 610 | 611 | impl MultiSelectView { 612 | /// Convenient method to use the label as value. 613 | pub fn add_item_str>(&mut self, label: S) { 614 | let label = label.into(); 615 | self.add_item(label.clone(), label); 616 | } 617 | 618 | /// Chainable variant of add_item_str 619 | pub fn item_str>(self, label: S) -> Self { 620 | self.with(|s| s.add_item_str(label)) 621 | } 622 | 623 | /// Convenient method to use the label as value. 624 | pub fn insert_item_str(&mut self, index: usize, label: S) 625 | where 626 | S: Into, 627 | { 628 | let label = label.into(); 629 | self.insert_item(index, label.clone(), label); 630 | } 631 | 632 | /// Adds all strings from an iterator. 633 | /// 634 | /// # Examples 635 | /// 636 | /// ``` 637 | /// # extern crate marcos; 638 | /// # use marcos::ui::multi_select::MultiSelectView; 639 | /// let mut select_view = MultiSelectView::new(); 640 | /// select_view.add_all_str(vec!["a", "b", "c"]); 641 | /// ``` 642 | pub fn add_all_str(&mut self, iter: I) 643 | where 644 | S: Into, 645 | I: IntoIterator, 646 | { 647 | for s in iter { 648 | self.add_item_str(s); 649 | } 650 | } 651 | 652 | /// Adds all strings from an iterator. 653 | /// 654 | /// Chainable variant. 655 | pub fn with_all_str(self, iter: I) -> Self 656 | where 657 | S: Into, 658 | I: IntoIterator, 659 | { 660 | self.with(|s| s.add_all_str(iter)) 661 | } 662 | } 663 | 664 | impl View for MultiSelectView { 665 | fn draw(&self, printer: &Printer) { 666 | self.last_offset.set(printer.offset); 667 | 668 | if self.popup { 669 | // Popup-select only draw the active element. 670 | // We'll draw the full list in a popup if needed. 671 | let style = if !self.enabled { 672 | ColorStyle::secondary() 673 | } else if !printer.focused { 674 | ColorStyle::primary() 675 | } else { 676 | ColorStyle::highlight() 677 | }; 678 | let x = match printer.size.x.checked_sub(1) { 679 | Some(x) => x, 680 | None => return, 681 | }; 682 | 683 | printer.with_color(style, |printer| { 684 | // Prepare the entire background 685 | printer.print_hline((1, 0), x, " "); 686 | // Draw the borders 687 | printer.print((0, 0), "<"); 688 | printer.print((x, 0), ">"); 689 | 690 | let label = &self.items[self.focus()].label; 691 | 692 | // And center the text? 693 | let offset = HAlign::Center.get_offset(label.len(), x + 1); 694 | 695 | printer.print((offset, 0), label); 696 | }); 697 | } else { 698 | // Non-popup mode: we always print the entire list. 699 | let h = self.items.len(); 700 | let offset = self.align.v.get_offset(h, printer.size.y); 701 | let printer = &printer.offset((0, offset)); 702 | 703 | for i in 0..self.len() { 704 | printer 705 | .offset((0, i)) 706 | .with_selection(i == self.focus(), |printer| { 707 | if i != self.focus() && !self.enabled { 708 | printer.with_color(ColorStyle::secondary(), |printer| { 709 | self.draw_item(printer, i) 710 | }); 711 | } else { 712 | self.draw_item(printer, i); 713 | } 714 | }); 715 | } 716 | } 717 | } 718 | 719 | fn required_size(&mut self, _: Vec2) -> Vec2 { 720 | // Items here are not compressible. 721 | // So no matter what the horizontal requirements are, 722 | // we'll still return our longest item. 723 | let w = self 724 | .items 725 | .iter() 726 | .map(|item| item.label.width()) 727 | .max() 728 | .unwrap_or(1); 729 | if self.popup { 730 | Vec2::new(w + 2, 1) 731 | } else { 732 | let h = self.items.len(); 733 | 734 | Vec2::new(w, h) 735 | } 736 | } 737 | 738 | fn on_event(&mut self, event: Event) -> EventResult { 739 | if self.popup { 740 | self.on_event_popup(event) 741 | } else { 742 | self.on_event_regular(event) 743 | } 744 | } 745 | 746 | fn take_focus(&mut self, _: Direction) -> bool { 747 | self.enabled && !self.items.is_empty() 748 | } 749 | 750 | fn layout(&mut self, size: Vec2) { 751 | self.last_size = size; 752 | } 753 | 754 | fn important_area(&self, size: Vec2) -> Rect { 755 | self.selected_id() 756 | .map(|i| Rect::from_size((0, i), (size.x, 1))) 757 | .unwrap_or_else(|| Rect::from((0, 0))) 758 | } 759 | } 760 | 761 | struct Item { 762 | label: String, 763 | value: Rc, 764 | } 765 | 766 | impl Item { 767 | fn new(label: String, value: T) -> Self { 768 | Item { 769 | label, 770 | value: Rc::new(value), 771 | } 772 | } 773 | } 774 | 775 | fn is_numeric(event: &Event) -> bool { 776 | match event { 777 | Event::Char(c) => if c.is_numeric() { 778 | return true; 779 | }, 780 | _ => {} 781 | } 782 | false 783 | } 784 | 785 | fn is_alphabete(event: &Event) -> bool { 786 | match event { 787 | Event::Char(c) => if c.is_alphabetic() { 788 | return true; 789 | }, 790 | _ => {} 791 | } 792 | false 793 | } 794 | 795 | fn get_number(seq: &Vec) -> usize { 796 | let mut ans = 0usize; 797 | let mut len = seq.len() as u32; 798 | for i in seq.into_iter() { 799 | ans += i * 10usize.pow(len - 1); 800 | len -= 1; 801 | } 802 | ans 803 | } 804 | 805 | fn should_intercept(event: &Event) -> bool { 806 | match event { 807 | Event::Char('g') => return true, 808 | Event::Char('G') => return true, 809 | _ => {} 810 | } 811 | false 812 | } 813 | -------------------------------------------------------------------------------- /src/ui/tab.rs: -------------------------------------------------------------------------------- 1 | use crate::error::*; 2 | use std::collections::HashMap; 3 | use std::path::PathBuf; 4 | 5 | /// Struct to hold a collection of 3 views, according to miller's columns. First, being the 6 | /// previous directory, then second directory, followed by preview window. 7 | #[allow(dead_code)] 8 | #[derive(Debug)] 9 | pub struct Tab { 10 | pub title: u32, 11 | 12 | // Views 13 | pub p_view: PathBuf, 14 | pub c_view: PathBuf, 15 | // pub preview: PathBuf, 16 | 17 | // Selected 18 | // TODO Currently, based on index, need to change to PathBuf 19 | pub focused: HashMap, 20 | // pub p_focused: usize, 21 | // pub c_focused: Option, 22 | // preview_selected: Vec, 23 | } 24 | 25 | impl Tab { 26 | /// Funtion to create a tab from given name and path 27 | pub fn from(title: u32, path: &PathBuf) -> Result { 28 | // TODO too much assumptions here. Need to clarify. 29 | debug!("Inside tab::from {:?}", path); 30 | let p_view: PathBuf = match path.to_str() { 31 | Some("/") => PathBuf::from("root"), 32 | Some(e) => PathBuf::from(e) 33 | .parent() 34 | .ok_or(ErrorKind::DirNotFound { 35 | dirname: format!("Parent for {:?}", e), 36 | })?.to_path_buf(), 37 | None => PathBuf::new(), 38 | }; 39 | // let parent_path = path.parent() 40 | // .ok_or(ErrorKind::DirNotFound{dirname: format!("Parent for: {:?}", path.to_str())})?; 41 | let c_view: PathBuf = PathBuf::from(path); 42 | 43 | Ok(Self { 44 | title, 45 | p_view, 46 | c_view, 47 | 48 | focused: HashMap::new(), 49 | // p_focused: 0, 50 | // c_focused: None, 51 | }) 52 | } 53 | 54 | pub fn go_back(&mut self) { 55 | let temp_path = PathBuf::from(&self.p_view); 56 | match temp_path.to_str() { 57 | Some("/") => { 58 | self.p_view = PathBuf::from("root"); 59 | self.c_view = PathBuf::from("/") 60 | } 61 | Some("root") => {} 62 | Some(c) => { 63 | debug!("Getting other..... {:?}", c); 64 | let path = PathBuf::from(c); 65 | match path.parent() { 66 | Some(d) => { 67 | self.p_view = d.to_path_buf(); 68 | self.c_view = PathBuf::from(c); 69 | } 70 | None => {} 71 | } 72 | } 73 | None => {} 74 | } 75 | debug!("{:?}, {:?}", self.p_view, self.c_view); 76 | } 77 | 78 | pub fn go_forward(&mut self, path: PathBuf) { 79 | self.c_view = PathBuf::from(&path); 80 | self.p_view = path.parent().unwrap().to_path_buf(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/utils/filter.rs: -------------------------------------------------------------------------------- 1 | //! Funtions to help in assisting filter of entries 2 | 3 | use walkdir::DirEntry; 4 | 5 | /// Returns true if entry is hidden, irrespective of type(file or directory) 6 | pub fn is_hidden(entry: &DirEntry) -> bool { 7 | entry 8 | .file_name() 9 | .to_str() 10 | .map(|s| s.starts_with(".")) 11 | .unwrap_or(false) 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/info.rs: -------------------------------------------------------------------------------- 1 | //! Module which contains information related to system paths, files, folders, available space, etc 2 | 3 | use std::fmt::Display; 4 | use std::path::Path; 5 | use std::ffi::OsString; 6 | 7 | use systemstat::{Platform, System}; 8 | use uname::uname; 9 | use users::get_current_username; 10 | 11 | /// Funtion to get user information as a String in the form: 12 | /// `username @ host` 13 | pub fn user_info() -> String { 14 | let user_name = get_current_username().unwrap_or(OsString::from("NA")); 15 | let user_data = uname().unwrap(); 16 | format!("{}@{}", user_name.into_string().unwrap(), user_data.nodename) 17 | } 18 | 19 | /// Function to get mount point info as a std::String in the form: 20 | /// `_path available_ of _total_` 21 | pub fn disk_info + Display>(path: P) -> String { 22 | let sys = System::new(); 23 | let mount_info: String = match sys.mount_at(&path) { 24 | Ok(mount) => format!("{} {} available of {}", path, mount.avail, mount.total), 25 | // TODO Make error string less verbose 26 | Err(_e) => String::from("Failed to read mount info!"), 27 | }; 28 | mount_info 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/logger.rs: -------------------------------------------------------------------------------- 1 | use fern; 2 | 3 | use crate::error::*; 4 | 5 | /// Initialize the looger. 6 | /// Currently lacks flexibility to specify log levels, output streams, etc 7 | /// 8 | /// TODO: Log level as input, output stream as input, default log file format 9 | pub fn init(file_name: Option<&str>, log_level: Option<&str>) -> Result<()> { 10 | let logger = fern::Dispatch::new() 11 | .format(|out, message, record| { 12 | out.finish(format_args!( 13 | "==> {} [{}] -> {}", 14 | record.target(), 15 | record.level(), 16 | message 17 | )) 18 | }).level_for("cursive", log::LevelFilter::Warn) 19 | .level(match log_level { 20 | Some(c) => match c { 21 | "debug" => log::LevelFilter::Debug, 22 | "info" => log::LevelFilter::Info, 23 | "error" => log::LevelFilter::Error, 24 | _ => log::LevelFilter::Off, 25 | }, 26 | None => log::LevelFilter::Off, 27 | }); 28 | //.chain(fern::log_file(file_name).expect("Incorrect Log file format")) 29 | //.apply()?; 30 | if let Some(c) = file_name { 31 | logger.chain(fern::log_file(c)?).apply()?; 32 | } else { 33 | logger.apply()?; 34 | } 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod filter; 2 | pub mod info; 3 | pub mod logger; 4 | -------------------------------------------------------------------------------- /theme.example.toml: -------------------------------------------------------------------------------- 1 | # Whether to show shadow around widgets. Possible values: true, false 2 | shadow = false 3 | 4 | # Type of borders. Possible values: "simple", "none", "outset" 5 | borders = "simple" 6 | 7 | [colors] 8 | 9 | # Background color to use. 10 | background = "black" 11 | 12 | # If the value is an array, the first valid color will be used. 13 | # If the terminal doesn't support custom color, 14 | # non-base colors will be skipped. 15 | shadow = ["#000000", "black"] 16 | 17 | # Color for each view, i.e. each of the _column_ which is displayed. 18 | view = "#d3d7cf" 19 | 20 | # Array and simple values have the same effect. 21 | primary = ["#111111"] 22 | secondary = "#EEEEEE" 23 | tertiary = "#444444" 24 | 25 | # Hex values can use lower or uppercase. 26 | # (base color MUST be lowercase) 27 | title_primary = "#ff5555" 28 | title_secondary = "#ffff55" 29 | 30 | # Lower precision values can use only 3 digits. 31 | highlight = "#F00" 32 | highlight_inactive = "#5555FF" 33 | --------------------------------------------------------------------------------