diff --git a/Cargo.lock b/Cargo.lock index 74865366..6aa79ad5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -344,6 +344,7 @@ dependencies = [ "sysinfo", "tempfile", "terminal_size", + "termion", "thousands", "unicode-width", "winapi-util", @@ -486,6 +487,7 @@ checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ "bitflags 2.9.1", "libc", + "redox_syscall", ] [[package]] @@ -562,6 +564,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "numtoa" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" + [[package]] name = "once_cell" version = "1.21.3" @@ -657,6 +665,21 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "redox_termios" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" + [[package]] name = "redox_users" version = "0.4.6" @@ -841,6 +864,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "termion" +version = "4.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb" +dependencies = [ + "libc", + "libredox", + "numtoa", + "redox_termios", +] + [[package]] name = "termtree" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 6df4d122..30bd8946 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ directories = "4" sysinfo = "0.27" ctrlc = "3.4" chrono = "0.4" +termion="4" [target.'cfg(not(target_has_atomic = "64"))'.dependencies] portable-atomic = "1.4" diff --git a/src/display.rs b/src/display.rs index d24af1d4..2b4c62fd 100644 --- a/src/display.rs +++ b/src/display.rs @@ -4,6 +4,7 @@ use crate::node::FileTime; use ansi_term::Colour::Red; use lscolors::{LsColors, Style}; +use termion::raw::RawTerminal; use unicode_width::UnicodeWidthStr; use stfu8::encode_u8; @@ -12,6 +13,8 @@ use chrono::{DateTime, Local, TimeZone, Utc}; use std::cmp::max; use std::cmp::min; use std::fs; +use std::io::Stdout; +use std::io::Write; use std::iter::repeat_n; use std::path::Path; use thousands::Separable; @@ -29,6 +32,7 @@ pub struct InitialDisplayData { pub is_screen_reader: bool, pub output_format: String, pub bars_on_right: bool, + pub selected_index: i32, } pub struct DisplayData { @@ -81,6 +85,7 @@ struct DrawData<'a> { display_data: &'a DisplayData, } + impl DrawData<'_> { fn get_new_indent(&self, has_children: bool, was_i_last: bool) -> String { let chars = self.display_data.get_tree_chars(was_i_last, has_children); @@ -129,6 +134,7 @@ pub fn draw_it( no_percent_bars: bool, terminal_width: usize, skip_total: bool, + stdout: &mut RawTerminal, ) { let num_chars_needed_on_left_most = if idd.by_filecount { let max_size = root_node.size; @@ -170,8 +176,14 @@ pub fn draw_it( display_data: &display_data, }; + let mut test = if display_data.initial.is_reversed { + recursive_child_count(root_node) + } else { + 0 + }; + if !skip_total { - display_node(root_node, &draw_data, true, true); + display_node(root_node, stdout, &draw_data, true, true, test); } else { for (count, c) in root_node .get_children_from_node(draw_data.display_data.initial.is_reversed) @@ -179,7 +191,13 @@ pub fn draw_it( { let is_biggest = display_data.is_biggest(count, root_node.num_siblings()); let was_i_last = display_data.is_last(count, root_node.num_siblings()); - display_node(c, &draw_data, is_biggest, was_i_last); + display_node(c, stdout, &draw_data, is_biggest, was_i_last, test); + // not yet tested: + if display_data.initial.is_reversed { + test += recursive_child_count(c); + } else { + test += recursive_child_count(c); + } } } } @@ -218,16 +236,46 @@ fn find_longest_dir_name( .fold(longest, max) } -fn display_node(node: &DisplayNode, draw_data: &DrawData, is_biggest: bool, is_last: bool) { +pub fn recursive_child_count(node: &DisplayNode) -> i32 { + let mut total = 1; + for n in node.children.iter() { + total += recursive_child_count(&n); + } + return total; +} + +fn display_node( + node: &DisplayNode, + stdout: &mut RawTerminal, + draw_data: &DrawData, + is_biggest: bool, + is_last: bool, + test: i32, +) { // hacky way of working out how deep we are in the tree let indent = draw_data.get_new_indent(!node.children.is_empty(), is_last); let level = ((indent.chars().count() - 1) / 2) - 1; let bar_text = draw_data.generate_bar(node, level); - let to_print = format_string(node, &indent, &bar_text, is_biggest, draw_data.display_data); + let cnt = if draw_data.display_data.initial.is_reversed { + recursive_child_count(node) + } else { + 0 + }; + + let to_print = format_string( + node, + &indent, + &bar_text, + is_biggest, + &draw_data.display_data, + test - cnt, + ); + let mut tt = test; if !draw_data.display_data.initial.is_reversed { - println!("{to_print}") + tt += 1; + write!(stdout, "{to_print}").unwrap() } let dd = DrawData { @@ -244,11 +292,17 @@ fn display_node(node: &DisplayNode, draw_data: &DrawData, is_biggest: bool, is_l { let is_biggest = dd.display_data.is_biggest(count, num_siblings); let was_i_last = dd.display_data.is_last(count, num_siblings); - display_node(c, &dd, is_biggest, was_i_last); + + display_node(c, stdout, &dd, is_biggest, was_i_last, tt); + if draw_data.display_data.initial.is_reversed { + tt -= recursive_child_count(c) + } else { + tt += recursive_child_count(c) + } } if draw_data.display_data.initial.is_reversed { - println!("{to_print}") + write!(stdout, "{to_print}").unwrap() } } @@ -325,18 +379,22 @@ pub fn format_string( bars: &str, is_biggest: bool, display_data: &DisplayData, + test: i32, ) -> String { let (percent, name_and_padding) = get_name_percent(node, indent, bars, display_data); - let pretty_size = get_pretty_size(node, is_biggest, display_data); + let pretty_size = get_pretty_size(node, is_biggest, display_data, test); let pretty_name = get_pretty_name(node, name_and_padding, display_data); + let marked = get_name_if_marked(test==display_data.initial.selected_index, pretty_name); + let indent = get_indent_if_marked(test==display_data.initial.selected_index, indent); + // we can clean this and the method below somehow, not sure yet if display_data.initial.is_screen_reader { // if screen_reader then bars is 'depth' - format!("{pretty_name} {bars} {pretty_size}{percent}") + format!("{marked} {bars} {pretty_size}{percent}") } else if display_data.initial.by_filetime.is_some() { - format!("{pretty_size} {indent}{pretty_name}") + format!("{pretty_size} {indent}{marked}") } else { - format!("{pretty_size} {indent} {pretty_name}{percent}") + format!("{pretty_size} {indent} {marked}{percent}") } } @@ -366,13 +424,19 @@ fn get_name_percent( } } -fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayData) -> String { +fn get_pretty_size( + node: &DisplayNode, + is_biggest: bool, + display_data: &DisplayData, + n: i32, +) -> String { let output = if display_data.initial.by_filecount { node.size.separate_with_commas() } else if display_data.initial.by_filetime.is_some() { get_pretty_file_modified_time(node.size as i64) } else { - human_readable_number(node.size, &display_data.initial.output_format) + // human_readable_number(n, &display_data.initial.output_format) + format!("{n}") }; let spaces_to_add = display_data.num_chars_needed_on_left_most - output.chars().count(); let output = " ".repeat(spaces_to_add) + output.as_str(); @@ -392,6 +456,33 @@ fn get_pretty_file_modified_time(timestamp: i64) -> String { local_datetime.format("%Y-%m-%dT%H:%M:%S").to_string() } +fn get_indent_if_marked(test: bool, indent: &str) -> String { + if test { + let mut new_name = String::new(); + for _ in indent.chars() { + new_name.push(BLOCKS[0]) + } + new_name + } else { + indent.into() + } +} +fn get_name_if_marked(test: bool, name: String) -> String { + if test { + let mut new_name = String::new(); + for c in name.chars() { + if c == ' ' { + new_name.push(BLOCKS[0]) + } else { + new_name.push(c) + } + } + new_name + } else { + name.into() + } +} + fn get_pretty_name( node: &DisplayNode, name_and_padding: String, diff --git a/src/main.rs b/src/main.rs index bbc7c005..24bb3639 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ mod utils; use crate::cli::Cli; use crate::config::Config; +use crate::display::recursive_child_count; use crate::display_node::DisplayNode; use crate::progress::RuntimeErrors; use clap::Parser; @@ -20,17 +21,27 @@ use display::InitialDisplayData; use filter::AggregateData; use progress::PIndicator; use regex::Error; +use std::cmp::min; use std::collections::HashSet; use std::env; use std::fs::read_to_string; use std::io; +use std::io::Stdout; +use std::io::stdin; +use std::io::stdout; use std::panic; use std::process; use std::sync::Arc; use std::sync::Mutex; use sysinfo::{System, SystemExt}; +use termion::raw::RawTerminal; use utils::canonicalize_absolute_path; +use std::io::Write; +use termion::event::{Event, Key}; +use termion::input::TermRead; +use termion::raw::IntoRawMode; + use self::display::draw_it; use config::get_config; use dir_walker::walk_it; @@ -309,24 +320,93 @@ fn main() { let print_errors = config.get_print_errors(&options); print_any_errors(print_errors, walk_data.errors); + let stdin = stdin(); + let mut out = stdout().into_raw_mode().unwrap(); + + write!( + out, + "{}{}Dust interactive (q to quit)", + termion::clear::All, + termion::cursor::Goto(1, 1) + ) + .unwrap(); + write!(out, "{}", termion::cursor::Goto(1, 2)).unwrap(); print_output( - config, - options, - tree, - walk_data.by_filecount, + &config, + &options, + &tree, is_colors, terminal_width, - ) - }); + &mut out, + 0, + ); + out.flush().unwrap(); + + let mut state = 0; + for c in stdin.events() { + write!( + out, + "{}{}Dust interactive (q to quit) {state}", + termion::clear::All, + termion::cursor::Goto(1, 1) + ) + .unwrap(); + write!(out, "{}", termion::cursor::Goto(1, 2)).unwrap(); + let evt = c.unwrap(); + match evt { + Event::Key(Key::Char('q')) => break, + Event::Key(Key::Up | Key::Char('k')) => { + write!(out, "up\n").unwrap(); + if !config.get_reverse(&options){ + state += 1; + } else { + state -= 1; + } + } + Event::Key(Key::Down | Key::Char('j')) => { + write!(out, "down\n").unwrap(); + if !config.get_reverse(&options){ + state -= 1; + } else { + state += 1; + } + } + Event::Key(Key::Left | Key::Char('h')) => { + write!(out, "left\n").unwrap(); + } + Event::Key(Key::Right | Key::Char('l')) => { + write!(out, "right\n").unwrap(); + } + Event::Key(Key::Char(x)) => { + write!(out, "{x} key\n").unwrap(); + } + _ => {} + } + state = max(0, state); + state = min(recursive_child_count(&tree)-1, state); + write!(out, "{}", termion::cursor::Goto(1, 3)).unwrap(); + print_output( + &config, + &options, + &tree, + is_colors, + terminal_width, + &mut out, + state, + ); + out.flush().unwrap(); + } + }) } fn print_output( - config: Config, - options: Cli, - tree: DisplayNode, - by_filecount: bool, + config: &Config, + options: &Cli, + tree: &DisplayNode, is_colors: bool, terminal_width: usize, + stdout: &mut RawTerminal, + selected_index: i32, ) { let output_format = config.get_output_format(&options); @@ -340,19 +420,21 @@ fn print_output( short_paths: !config.get_full_paths(&options), is_reversed: !config.get_reverse(&options), colors_on: is_colors, - by_filecount, + by_filecount: options.filecount, by_filetime: config.get_filetime(&options), is_screen_reader: config.get_screen_reader(&options), output_format, bars_on_right: config.get_bars_on_right(&options), + selected_index: selected_index, }; draw_it( idd, - &tree, + tree, config.get_no_bars(&options), terminal_width, config.get_skip_total(&options), + stdout, ) } }