summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2020-11-05 17:09:18 +0100
committerCanop <cano.petrole@gmail.com>2020-11-05 17:09:18 +0100
commite47ce676b0fe285f782d3a5282b8658b9b3568e5 (patch)
tree01a4be68305fd1ab85844fe1c5b8d105fb035a3d
parentcf6eab4a8ad7f1cd8881a9f4203bb8aaddb20e28 (diff)
preview now supports opening zero length "files"v1.0.5
(for exemple the ones of /proc)
-rw-r--r--CHANGELOG.md1
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml1
-rw-r--r--src/display/crop.rs66
-rw-r--r--src/display/crop_writer.rs11
-rw-r--r--src/errors.rs1
-rw-r--r--src/hex/hex_view.rs1
-rw-r--r--src/lib.rs2
-rw-r--r--src/preview/mod.rs2
-rw-r--r--src/preview/preview.rs10
-rw-r--r--src/preview/zero_len_file_view.rs56
-rw-r--r--src/syntactic/syntactic_view.rs27
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
diff --git a/Cargo.lock b/Cargo.lock
index 7e79aad..a51e6b8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index ddeaee0..fffed90 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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,
diff --git a/src/lib.rs b/src/lib.rs
index 4363c72..c5cf474 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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
+ })
}