diff options
author | Canop <cano.petrole@gmail.com> | 2020-11-05 17:09:18 +0100 |
---|---|---|
committer | Canop <cano.petrole@gmail.com> | 2020-11-05 17:09:18 +0100 |
commit | e47ce676b0fe285f782d3a5282b8658b9b3568e5 (patch) | |
tree | 01a4be68305fd1ab85844fe1c5b8d105fb035a3d | |
parent | cf6eab4a8ad7f1cd8881a9f4203bb8aaddb20e28 (diff) |
preview now supports opening zero length "files"v1.0.5
(for exemple the ones of /proc)
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | Cargo.lock | 7 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/display/crop.rs | 66 | ||||
-rw-r--r-- | src/display/crop_writer.rs | 11 | ||||
-rw-r--r-- | src/errors.rs | 1 | ||||
-rw-r--r-- | src/hex/hex_view.rs | 1 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/preview/mod.rs | 2 | ||||
-rw-r--r-- | src/preview/preview.rs | 10 | ||||
-rw-r--r-- | src/preview/zero_len_file_view.rs | 56 | ||||
-rw-r--r-- | src/syntactic/syntactic_view.rs | 27 |
12 files changed, 150 insertions, 35 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 93bf611..7ff00ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ### next version * in case of IO error when previewing a file, display the error instead of quitting * fix regression related to display of texts with characters taking several columns +* preview now supports opening system files with size 0 (eg /proc "files") <a name="v1.0.4"></a> ### v1.0.4 - 2020-10-22 @@ -119,6 +119,7 @@ version = "1.0.5-dev" dependencies = [ "ansi_colours", "bet", + "char_reader", "chrono", "clap", "criterion", @@ -211,6 +212,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] +name = "char_reader" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daa67493227adb60f22eb6dca4dc94d7a2a526b38eab5c1831d617944eb1a07" + +[[package]] name = "chrono" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -21,6 +21,7 @@ clipboard = ["terminal-clipboard"] [dependencies] ansi_colours = "1.0" bet = "0.3.4" +char_reader = "0.1" clap = { version="2.33", default-features=false, features=["suggestions"] } chrono = "0.4" crossbeam = "0.7" diff --git a/src/display/crop.rs b/src/display/crop.rs index 38a1ad0..c060739 100644 --- a/src/display/crop.rs +++ b/src/display/crop.rs @@ -1,22 +1,62 @@ use { + super::TAB_REPLACEMENT, unicode_width::UnicodeWidthChar, }; -// return the counts in bytes and columns of the longest substring -// fitting the given number of columns -pub fn count_fitting(s: &str, columns_max: usize) -> (usize, usize) { - let mut count_bytes = 0; - let mut str_width = 0; - for (idx, c) in s.char_indices() { - let char_width = UnicodeWidthChar::width(c).unwrap_or(0); - let next_str_width = str_width + char_width; - if next_str_width > columns_max { - break; + +#[derive(Debug, Clone, Copy)] +pub struct StrFit { + bytes_count: usize, + cols_count: usize, + has_tab: bool, +} + +impl StrFit { + pub fn from(s: &str, cols_max: usize) -> Self { + let mut bytes_count = 0; + let mut cols_count = 0; + let mut has_tab = false; + for (idx, c) in s.char_indices() { + let char_width = if '\t' == c { + has_tab = true; + TAB_REPLACEMENT.len() + } else { + UnicodeWidthChar::width(c).unwrap_or(0) + }; + let next_str_width = cols_count + char_width; + if next_str_width > cols_max { + break; + } + cols_count = next_str_width; + bytes_count = idx + c.len_utf8(); } - str_width = next_str_width; - count_bytes = idx + c.len_utf8(); + Self { + bytes_count, + cols_count, + has_tab, + } + } +} + +/// return the counts in bytes and columns of the longest substring +/// fitting the given number of columns +pub fn count_fitting(s: &str, cols_max: usize) -> (usize, usize) { + let fit = StrFit::from(s, cols_max); + (fit.bytes_count, fit.cols_count) +} + +/// return both the longest fitting string and the number of cols +/// it takes on screen. +/// We don't build a string around the whole str, which could be costly +/// if it's very big +pub fn make_string(s: &str, cols_max: usize) -> (String, usize) { + let fit = StrFit::from(s, cols_max); + if fit.has_tab { + let string = (&s[0..fit.bytes_count]).replace('\t', TAB_REPLACEMENT); + (string, fit.cols_count) + } else { + (s[0..fit.bytes_count].to_string(), fit.cols_count) } - (count_bytes, str_width) } #[cfg(test)] diff --git a/src/display/crop_writer.rs b/src/display/crop_writer.rs index 1664564..98a43be 100644 --- a/src/display/crop_writer.rs +++ b/src/display/crop_writer.rs @@ -12,14 +12,14 @@ use { }; -/// wrap a writer to ensure that at most `allowed` chars are +/// wrap a writer to ensure that at most `allowed` columns are /// written. -/// Note: tab replacement managment is only half designed/coded pub struct CropWriter<'w, W> where W: std::io::Write, { pub w: &'w mut W, + /// number of screen columns which may be covered pub allowed: usize, } @@ -33,11 +33,10 @@ where pub fn is_full(&self) -> bool { self.allowed == 0 } + /// return a tuple containing a string containing either the given &str + /// or the part fitting the remaining width, and the width of this string) pub fn cropped_str(&self, s: &str) -> (String, usize) { - let mut string = s.replace('\t', TAB_REPLACEMENT); - let (count_bytes, count_chars) = crop::count_fitting(&string, self.allowed); - string.truncate(count_bytes); - (string, count_chars) + crop::make_string(s, self.allowed) } pub fn queue_unstyled_str(&mut self, s: &str) -> Result<()> { if self.is_full() { diff --git a/src/errors.rs b/src/errors.rs index 0c1ec7c..508f9d8 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -26,6 +26,7 @@ custom_error! {pub ProgramError NetError {source: NetError} = "{}", ImageError {source: ImageError } = "{}", Lfs {details: String} = "Failed to fetch mounts: {}", + ZeroLenFile = "File seems empty", } custom_error! {pub TreeBuildError diff --git a/src/hex/hex_view.rs b/src/hex/hex_view.rs index 75005a8..0888a67 100644 --- a/src/hex/hex_view.rs +++ b/src/hex/hex_view.rs @@ -1,4 +1,3 @@ - use { super::{ byte::Byte, @@ -1,5 +1,3 @@ -// #![ allow( dead_code, unused_imports ) ] - #[macro_use] extern crate crossbeam; #[macro_use] diff --git a/src/preview/mod.rs b/src/preview/mod.rs index ed49778..0eea813 100644 --- a/src/preview/mod.rs +++ b/src/preview/mod.rs @@ -1,9 +1,11 @@ mod preview; mod preview_state; +mod zero_len_file_view; pub use { preview::Preview, preview_state::PreviewState, + zero_len_file_view::ZeroLenFileView, }; #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/src/preview/preview.rs b/src/preview/preview.rs index ecf6850..19c444e 100644 --- a/src/preview/preview.rs +++ b/src/preview/preview.rs @@ -1,6 +1,5 @@ - use { - super::PreviewMode, + super::*, crate::{ app::{AppContext, LineNumber}, command::{ScrollCommand}, @@ -25,6 +24,7 @@ pub enum Preview { Image(ImageView), Syntactic(SyntacticView), Hex(HexView), + ZeroLen(ZeroLenFileView), IOError(io::Error), } @@ -87,6 +87,10 @@ impl Preview { ) -> Self { match SyntacticView::new(path, InputPattern::none(), &mut Dam::unlimited(), con) { Ok(Some(sv)) => Self::Syntactic(sv), + Err(ProgramError::ZeroLenFile) => { + debug!("zero len file - check if system file"); + Self::ZeroLen(ZeroLenFileView::new(path.to_path_buf())) + } // not previewable as UTF8 text // we'll try reading it as binary _ => Self::hex(path), @@ -136,6 +140,7 @@ impl Preview { match self { Self::Image(_) => Some(PreviewMode::Image), Self::Syntactic(_) => Some(PreviewMode::Text), + Self::ZeroLen(_) => Some(PreviewMode::Text), Self::Hex(_) => Some(PreviewMode::Hex), Self::IOError(_) => None, } @@ -226,6 +231,7 @@ impl Preview { match self { Self::Image(iv) => iv.display(w, screen, panel_skin, area, con), Self::Syntactic(sv) => sv.display(w, screen, panel_skin, area, con), + Self::ZeroLen(zlv) => zlv.display(w, screen, panel_skin, area), Self::Hex(hv) => hv.display(w, screen, panel_skin, area), Self::IOError(err) => { let mut y = area.top; diff --git a/src/preview/zero_len_file_view.rs b/src/preview/zero_len_file_view.rs new file mode 100644 index 0000000..5ec3045 --- /dev/null +++ b/src/preview/zero_len_file_view.rs @@ -0,0 +1,56 @@ +use { + crate::{ + display::{CropWriter, SPACE_FILLING, Screen, W}, + errors::ProgramError, + skin::PanelSkin, + }, + char_reader::CharReader, + crossterm::{ + cursor, + QueueableCommand, + }, + std::{ + fs::File, + path::PathBuf, + }, + termimad::{Area}, +}; + +/// a (light) display for a file declaring a size 0, +/// as happens for many system "files", for example in /proc +pub struct ZeroLenFileView { + path: PathBuf, +} + +impl ZeroLenFileView { + pub fn new(path: PathBuf) -> Self { + Self { + path, + } + } + pub fn display( + &mut self, + w: &mut W, + _screen: Screen, + panel_skin: &PanelSkin, + area: &Area, + ) -> Result<(), ProgramError> { + let styles = &panel_skin.styles; + let line_count = area.height as usize; + let file = File::open(&self.path)?; + let mut reader = CharReader::new(file); + // line_len here is in chars, and we crop in cols, but it's OK because both + // are usually identical for system files and we crop later anyway + let line_len = area.width as usize; + for y in 0..line_count { + w.queue(cursor::MoveTo(area.left, y as u16 + area.top))?; + let mut cw = CropWriter::new(w, area.width as usize); + let cw = &mut cw; + if let Some(line) = reader.next_line(line_len, 15_000)? { + cw.queue_str(&styles.default, &line)?; + } + cw.fill(&styles.default, &SPACE_FILLING)?; + } + Ok(()) + } +} diff --git a/src/syntactic/syntactic_view.rs b/src/syntactic/syntactic_view.rs index b2aa8ff..12a2393 100644 --- a/src/syntactic/syntactic_view.rs +++ b/src/syntactic/syntactic_view.rs @@ -4,7 +4,7 @@ use { app::{AppContext, LineNumber}, command::{ScrollCommand}, display::{CropWriter, SPACE_FILLING, Screen, W}, - errors::ProgramError, + errors::*, pattern::{InputPattern, NameMatch}, skin::PanelSkin, task_sync::Dam, @@ -17,7 +17,7 @@ use { memmap::Mmap, std::{ fs::File, - io::{self, BufRead, BufReader}, + io::{BufRead, BufReader}, path::{Path, PathBuf}, str, }, @@ -80,7 +80,7 @@ impl SyntacticView { pattern: InputPattern, dam: &mut Dam, con: &AppContext, - ) -> io::Result<Option<Self>> { + ) -> Result<Option<Self>, ProgramError> { let mut sv = Self { path: path.to_path_buf(), pattern, @@ -99,9 +99,17 @@ impl SyntacticView { } /// return true when there was no interruption - fn read_lines(&mut self, dam: &mut Dam, con: &AppContext) -> io::Result<bool> { + fn read_lines( + &mut self, + dam: &mut Dam, + con: &AppContext, + ) -> Result<bool, ProgramError> { let f = File::open(&self.path)?; - let with_style = f.metadata()?.len() < MAX_SIZE_FOR_STYLING; + let md = f.metadata()?; + if md.len() == 0 { + return Err(ProgramError::ZeroLenFile); + } + let with_style = md.len() < MAX_SIZE_FOR_STYLING; let mut reader = BufReader::new(f); self.lines.clear(); let mut line = String::new(); @@ -414,12 +422,9 @@ impl SyntacticView { } fn is_thumb(y: usize, scrollbar: Option<(u16, u16)>) -> bool { - if let Some((sctop, scbottom)) = scrollbar { + scrollbar.map_or(false, |(sctop, scbottom)| { let y = y as u16; - if sctop <= y && y <= scbottom { - return true; - } - } - false + sctop <= y && y <= scbottom + }) } |