Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ use std::{fmt, path::PathBuf, str::FromStr};
/// ```toml
/// format = "binary"
///
/// # Show directories as `dir/` instead of `/dir` in interactive mode.
/// # If unset, behavior defaults to false.
/// # directory_suffix = false
///
/// # Controls whether Git-ignored entry detection is enabled in interactive mode.
/// # Supported values: true, false.
/// # If unset, behavior defaults to true.
Expand All @@ -38,6 +42,10 @@ pub struct Config {
/// Byte count format to use when `--format` and `DUA_FORMAT` are not set.
pub format: Option<crate::ByteFormat>,

/// Whether directories are shown as `dir/` instead of `/dir` in interactive mode.
/// If unset, defaults to `false`.
pub directory_suffix: bool,

/// Keybinding-related settings.
pub keys: KeysConfig,

Expand Down Expand Up @@ -616,6 +624,10 @@ impl Config {
"# Supported values: metric, binary, bytes, gb, gib, mb, mib.\n",
"# format = \"binary\"\n",
"#\n",
"# Show directories as `dir/` instead of `/dir` (NCDU style) in interactive mode.\n",
"# If unset, behavior defaults to false.\n",
"# directory_suffix = false\n",
"#\n",
"# Controls whether Git-ignored entry detection is enabled in interactive mode.\n",
"# Supported values: true, false.\n",
"# If unset, behavior defaults to true.\n",
Expand Down
75 changes: 46 additions & 29 deletions src/interactive/widgets/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub struct EntriesProps<'a> {
pub current_path: PathBuf,
/// Size display mode used for byte and percentage columns.
pub display: DisplayOptions,
/// Whether directories are shown as `dir/` instead of `/dir`.
pub directory_suffix: bool,
/// Currently selected tree entry, if one is selected.
pub selected: Option<TreeIndex>,
/// Entries to display in the pane, already sorted for the current view.
Expand Down Expand Up @@ -70,6 +72,7 @@ impl Entries {
let EntriesProps {
current_path,
display,
directory_suffix,
entries,
selected,
marked,
Expand Down Expand Up @@ -154,7 +157,7 @@ impl Entries {
) as usize;

let name = shorten_input(
name_with_prefix(name.to_string_lossy(), *is_dir),
name_with_directory_marker(name.to_string_lossy(), *is_dir, *directory_suffix),
available_width,
);
let style = name_style(
Expand Down Expand Up @@ -571,34 +574,26 @@ fn fill_background_to_right(mut s: Cow<'_, str>, entire_width: u16) -> Cow<'_, s
}
}

fn name_with_prefix(mut name: Cow<'_, str>, is_dir: bool) -> Cow<'_, str> {
let prefix = if is_dir {
// Note that these names never happen on non-root items, so this is a root-item special case.
// It was necessary since we can't trust the 'actual' root anymore as it might be the CWD or
// `main()` cwd' into the one path that was provided by the user.
// The idea was to keep explicit roots as specified without adjustment, which works with this
// logic unless somebody provides `name` as is, then we will prefix it which is a little confusing.
// Overall, this logic makes the folder display more consistent.
if name == "."
|| name == ".."
|| name.starts_with('/')
|| name.starts_with("./")
|| name.starts_with("../")
{
None
} else {
Some("/")
}
} else {
Some(" ")
};
match prefix {
None => name,
Some(prefix) => {
name.to_mut().insert_str(0, prefix);
name
fn name_with_directory_marker(
mut name: Cow<'_, str>,
is_dir: bool,
directory_suffix: bool,
) -> Cow<'_, str> {
if directory_suffix {
if is_dir && !name.chars().last().is_some_and(std::path::is_separator) {
name.to_mut().push('/');
}
}
} else if !is_dir {
name.to_mut().insert(0, ' ');
} else if name != "."
&& name != ".."
&& !name.starts_with('/')
&& !name.starts_with("./")
&& !name.starts_with("../")
{
name.to_mut().insert(0, '/');
}
name
}

fn name_style(
Expand Down Expand Up @@ -776,7 +771,10 @@ mod entries_test {
use std::collections::HashSet;
use std::path::Path;

use super::{name_style, shorten_input, show_mtime_column, title as entry_title};
use super::{
name_style, name_with_directory_marker, shorten_input, show_mtime_column,
title as entry_title,
};
use crate::interactive::widgets::{Column, Language};
use crate::interactive::{MTimeSort, SortMode};
use dua::ByteFormat;
Expand Down Expand Up @@ -811,6 +809,25 @@ mod entries_test {
}
}

#[test]
fn directory_marker_can_be_moved_to_a_suffix_by_config() {
let suffix = toml::from_str::<dua::Config>("directory_suffix = true").unwrap();
for (config, directory, file) in [
(dua::Config::default(), "/dir", " file"),
(suffix, "dir/", "file"),
] {
assert_eq!(
name_with_directory_marker("dir".into(), true, config.directory_suffix),
directory
);
assert_eq!(
name_with_directory_marker("file".into(), false, config.directory_suffix),
file
);
}
assert_eq!(name_with_directory_marker("/".into(), true, true), "/");
}

#[test]
fn title_drops_statistics_before_shortening_path() {
let path = "a/b/c";
Expand Down
1 change: 1 addition & 0 deletions src/interactive/widgets/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ impl MainWindow {
let props = EntriesProps {
current_path: current_path.clone(),
display: *display,
directory_suffix: config.directory_suffix,
entries: &state.entries,
marked,
cleanup_candidates: state.cleanup_candidates.as_ref(),
Expand Down
Loading