summaryrefslogtreecommitdiffstats
path: root/src/util
diff options
context:
space:
mode:
authorDLFW <daniel@llin.info>2021-06-20 01:14:10 +0200
committerGitHub <noreply@github.com>2021-06-19 19:14:10 -0400
commite6359a599e804031189eb9bc067c4eff9b01a1a8 (patch)
treef94102b28145f91db5b0bded5e38869157c1946f /src/util
parent362a507ad60ee479fb68a48fc7daf4a49652f1bf (diff)
Show symlinked files and dirs correctly (#72)
* Treat symlinks as normal files and dirs * Dirs are recognized as dirs no matter is they are a symlink (correct devicon) * Permission flags shown in the footer are the permissions of the target in case a symlink is selected * Size of a symlinked file is the size of the target * File display to be fixed: symlink arrow now just overrides the file size, but both need to be shown one after the other * Correctly show symlink arrow and file size next This commit includes quite some refactoring. The shortening of the left and right part of an entry in a dir list and the shortening of the file name are separated into functions which don't directly write to the buffer but just return strings. That way, they get testable and further enhancements like different line modes should be easier to implement. * fix review findings * better file name truncation File names are only truncated at grapheme border, while the width is calculated on the actual nuber of terminal cells. * more adequate test organization * more code structure
Diffstat (limited to 'src/util')
-rw-r--r--src/util/mod.rs1
-rw-r--r--src/util/string.rs67
-rw-r--r--src/util/style.rs46
3 files changed, 98 insertions, 16 deletions
diff --git a/src/util/mod.rs b/src/util/mod.rs
index 35f264c..53b5512 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -10,6 +10,7 @@ pub mod name_resolution;
pub mod search;
pub mod select;
pub mod sort;
+pub mod string;
pub mod style;
pub mod to_string;
pub mod unix;
diff --git a/src/util/string.rs b/src/util/string.rs
new file mode 100644
index 0000000..e2e0002
--- /dev/null
+++ b/src/util/string.rs
@@ -0,0 +1,67 @@
+use unicode_segmentation::UnicodeSegmentation;
+use unicode_width::UnicodeWidthStr;
+
+///Truncates a string to width, less or equal to the specified one.
+///
+///In case the point of truncation falls into a full-width character,
+///the returned string will be shorter than the given `width`.
+///Otherwise, it will be equal.
+pub trait UnicodeTruncate {
+ fn trunc(&self, width: usize) -> String;
+}
+
+impl UnicodeTruncate for str {
+ #[inline]
+ fn trunc(&self, width: usize) -> String {
+ if self.width() <= width {
+ String::from(self)
+ } else {
+ let mut length: usize = 0;
+ let mut result = String::new();
+ for grapheme in self.graphemes(true) {
+ let grapheme_length = grapheme.width();
+ length += grapheme_length;
+ if length > width {
+ break;
+ };
+ result.push_str(grapheme);
+ }
+ result
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests_trunc {
+ use super::UnicodeTruncate;
+
+ #[test]
+ fn truncate_correct_despite_several_multibyte_chars() {
+ assert_eq!(String::from("r͂o͒͜w̾").trunc(2), String::from("r͂o͒͜"));
+ }
+
+ #[test]
+ fn truncate_at_end_returns_complete_string() {
+ assert_eq!(String::from("r͂o͒͜w̾").trunc(3), String::from("r͂o͒͜w̾"));
+ }
+
+ #[test]
+ fn truncate_behind_end_returns_complete_string() {
+ assert_eq!(String::from("r͂o͒͜w̾").trunc(4), String::from("r͂o͒͜w̾"));
+ }
+
+ #[test]
+ fn truncate_at_zero_returns_empty_string() {
+ assert_eq!(String::from("r͂o͒͜w̾").trunc(0), String::from(""));
+ }
+
+ #[test]
+ fn truncate_correct_despite_fullwidth_character() {
+ assert_eq!(String::from("a🌕bc").trunc(4), String::from("a🌕b"));
+ }
+
+ #[test]
+ fn truncate_within_fullwidth_character_truncates_before_the_character() {
+ assert_eq!(String::from("a🌕").trunc(2), String::from("a"));
+ }
+}
diff --git a/src/util/style.rs b/src/util/style.rs
index e04daf0..69a3704 100644
--- a/src/util/style.rs
+++ b/src/util/style.rs
@@ -1,6 +1,6 @@
use tui::style::Style;
-use crate::fs::{FileType, JoshutoDirEntry};
+use crate::fs::{FileType, JoshutoDirEntry, LinkType};
use crate::util::unix;
use crate::THEME_T;
@@ -8,25 +8,39 @@ use crate::THEME_T;
pub fn entry_style(entry: &JoshutoDirEntry) -> Style {
let metadata = &entry.metadata;
let filetype = &metadata.file_type();
+ let linktype = &metadata.link_type();
- match filetype {
- _ if entry.is_selected() => Style::default()
+ if entry.is_selected() {
+ Style::default()
.fg(THEME_T.selection.fg)
.bg(THEME_T.selection.bg)
- .add_modifier(THEME_T.selection.modifier),
- FileType::Directory => Style::default()
- .fg(THEME_T.directory.fg)
- .bg(THEME_T.directory.bg)
- .add_modifier(THEME_T.directory.modifier),
- FileType::Symlink(_) => Style::default()
- .fg(THEME_T.link.fg)
- .bg(THEME_T.link.bg)
- .add_modifier(THEME_T.link.modifier),
- _ if unix::is_executable(metadata.mode) => Style::default()
+ .add_modifier(THEME_T.selection.modifier)
+ } else {
+ match linktype {
+ LinkType::Symlink(_) => Style::default()
+ .fg(THEME_T.link.fg)
+ .bg(THEME_T.link.bg)
+ .add_modifier(THEME_T.link.modifier),
+ LinkType::Normal => match filetype {
+ FileType::Directory => Style::default()
+ .fg(THEME_T.directory.fg)
+ .bg(THEME_T.directory.bg)
+ .add_modifier(THEME_T.directory.modifier),
+ FileType::File => file_style(entry),
+ },
+ }
+ }
+}
+
+fn file_style(entry: &JoshutoDirEntry) -> Style {
+ let metadata = &entry.metadata;
+ if unix::is_executable(metadata.mode) {
+ Style::default()
.fg(THEME_T.executable.fg)
.bg(THEME_T.executable.bg)
- .add_modifier(THEME_T.executable.modifier),
- _ => match entry.file_path().extension() {
+ .add_modifier(THEME_T.executable.modifier)
+ } else {
+ match entry.file_path().extension() {
None => Style::default(),
Some(os_str) => match os_str.to_str() {
None => Style::default(),
@@ -35,6 +49,6 @@ pub fn entry_style(entry: &JoshutoDirEntry) -> Style {
Some(t) => Style::default().fg(t.fg).bg(t.bg).add_modifier(t.modifier),
},
},
- },
+ }
}
}