1 use {
2     super::byte::Byte,
3     crate::{
4         command::ScrollCommand,
5         display::{Screen, W},
6         errors::ProgramError,
7         skin::PanelSkin,
8     },
9     crossterm::{
10         cursor,
11         style::{Color, Print, SetForegroundColor},
12         QueueableCommand,
13     },
14     memmap::Mmap,
15     std::{
16         fs::File,
17         io,
18         path::PathBuf,
19     },
20     termimad::{Area, CropWriter, SPACE_FILLING},
21 };
22 
23 
24 pub struct HexLine {
25     pub bytes: Vec<u8>, // from 1 to 16 bytes
26 }
27 
28 /// a preview showing the content of a file in hexa
29 pub struct HexView {
30     path: PathBuf,
31     len: usize,
32     scroll: usize,
33     page_height: usize,
34 }
35 
36 impl HexView {
new(path: PathBuf) -> io::Result<Self>37     pub fn new(path: PathBuf) -> io::Result<Self> {
38         let len = path.metadata()?.len() as usize;
39         Ok(Self {
40             path,
41             len,
42             scroll: 0,
43             page_height: 0,
44         })
45     }
line_count(&self) -> usize46     pub fn line_count(&self) -> usize {
47         self.len / 16 + if self.len % 16 != 0 { 1 } else { 0 }
48     }
try_scroll( &mut self, cmd: ScrollCommand, ) -> bool49     pub fn try_scroll(
50         &mut self,
51         cmd: ScrollCommand,
52     ) -> bool {
53         let old_scroll = self.scroll;
54         self.scroll = cmd.apply(self.scroll, self.line_count(), self.page_height);
55         self.scroll != old_scroll
56     }
select_first(&mut self)57     pub fn select_first(&mut self) {
58         self.scroll = 0;
59     }
select_last(&mut self)60     pub fn select_last(&mut self) {
61         if self.page_height < self.line_count() {
62             self.scroll = self.line_count() - self.page_height;
63         }
64     }
get_page( &mut self, start_line_idx: usize, line_count: usize, ) -> io::Result<Vec<HexLine>>65     pub fn get_page(
66         &mut self,
67         start_line_idx: usize,
68         line_count: usize,
69     ) -> io::Result<Vec<HexLine>> {
70         let file = File::open(&self.path)?;
71         let mut lines = Vec::new();
72         if self.len == 0 {
73             return Ok(lines);
74         }
75         let mmap = unsafe { Mmap::map(&file)? };
76         let new_len = mmap.len();
77         if new_len != self.len {
78             warn!("previewed file len changed from {} to {}", self.len, new_len);
79             self.len = new_len;
80         }
81         let mut start_idx = 16 * start_line_idx;
82         while start_idx < self.len {
83             let line_len = 16.min(self.len - start_idx);
84             let mut bytes: Vec<u8> = vec![0; line_len];
85             bytes[0..line_len].copy_from_slice(&mmap[start_idx..start_idx + line_len]);
86             lines.push(HexLine { bytes });
87             if lines.len() >= line_count {
88                 break;
89             }
90             start_idx += line_len;
91         }
92         Ok(lines)
93     }
display( &mut self, w: &mut W, _screen: Screen, panel_skin: &PanelSkin, area: &Area, ) -> Result<(), ProgramError>94     pub fn display(
95         &mut self,
96         w: &mut W,
97         _screen: Screen,
98         panel_skin: &PanelSkin,
99         area: &Area,
100     ) -> Result<(), ProgramError> {
101         let line_count = area.height as usize;
102         self.page_height = area.height as usize;
103         let page = self.get_page(self.scroll, line_count)?;
104         let addresses_len = if self.len < 0xffff {
105             4
106         } else if self.len < 0xffffff {
107             6
108         } else {
109             8
110         };
111         let mut margin_around_adresses = false;
112         let styles = &panel_skin.styles;
113         let mut left_margin = false;
114         let mut addresses = false;
115         let mut hex_middle_space = false;
116         let mut chars_middle_space = false;
117         let mut inter_hex = false;
118         let mut chars = false;
119         let mut rem = area.width as i32 - 32; // 32: minimum, tight
120         if rem > 17 {
121             chars = true;
122             rem -= 17;
123         }
124         if rem > 16 {
125             inter_hex = true;
126             rem -= 16;
127         }
128         if rem > addresses_len {
129             addresses = true;
130             rem -= addresses_len;
131         }
132         if rem > 1 {
133             hex_middle_space = true;
134             rem -= 1;
135         }
136         if rem > 1 {
137             left_margin = true;
138             rem -= 1;
139         }
140         if rem > 1 {
141             chars_middle_space = true;
142             rem -= 1;
143         }
144         if addresses && rem >= 2 {
145             margin_around_adresses = true;
146             //rem -= 2;
147         }
148         let scrollbar = area.scrollbar(self.scroll, self.line_count());
149         let scrollbar_fg = styles.scrollbar_thumb.get_fg()
150             .or_else(|| styles.preview.get_fg())
151             .unwrap_or(Color::White);
152         for y in 0..line_count {
153             w.queue(cursor::MoveTo(area.left, y as u16 + area.top))?;
154             let mut cw = CropWriter::new(w, area.width as usize - 1); // -1 for scrollbar
155             let cw = &mut cw;
156             if y < page.len() {
157                 if addresses {
158                     let addr = (self.scroll + y) * 16;
159                     cw.queue_g_string(
160                         &styles.preview_line_number,
161                         match (addresses_len, margin_around_adresses) {
162                             (4, false) => format!("{:04x}", addr),
163                             (6, false) => format!("{:06x}", addr),
164                             (_, false) => format!("{:08x}", addr),
165                             (4, true) => format!(" {:04x} ", addr),
166                             (6, true) => format!(" {:06x} ", addr),
167                             (_, true) => format!(" {:08x} ", addr),
168                         },
169                     )?;
170                 }
171                 if left_margin {
172                     cw.queue_char(&styles.default, ' ')?;
173                 }
174                 let line = &page[y];
175                 for x in 0..16 {
176                     if x == 8 && hex_middle_space {
177                         cw.queue_char(&styles.default, ' ')?;
178                     }
179                     if let Some(b) = line.bytes.get(x) {
180                         let byte = Byte::from(*b);
181                         if inter_hex {
182                             cw.queue_g_string(byte.style(styles), format!("{:02x} ", b))?;
183                         } else {
184                             cw.queue_g_string(byte.style(styles), format!("{:02x}", b))?;
185                         }
186                     } else {
187                         cw.queue_str(&styles.default, if inter_hex { "   " } else { "  " })?;
188                     }
189                 }
190                 if chars {
191                     cw.queue_char(&styles.default, ' ')?;
192                     for x in 0..16 {
193                         if x == 8 && chars_middle_space {
194                             cw.queue_char(&styles.default, ' ')?;
195                         }
196                         if let Some(b) = line.bytes.get(x) {
197                             let byte = Byte::from(*b);
198                             cw.queue_char(byte.style(styles), byte.as_char())?;
199                         }
200                     }
201                 }
202             }
203             cw.fill(&styles.default, &SPACE_FILLING)?;
204             if is_thumb(y as u16 + area.top, scrollbar) {
205                 w.queue(SetForegroundColor(scrollbar_fg))?;
206                 w.queue(Print('▐'))?;
207             } else {
208                 w.queue(Print(' '))?;
209             }
210         }
211         Ok(())
212     }
213 
display_info( &mut self, w: &mut W, _screen: Screen, panel_skin: &PanelSkin, area: &Area, ) -> Result<(), ProgramError>214     pub fn display_info(
215         &mut self,
216         w: &mut W,
217         _screen: Screen,
218         panel_skin: &PanelSkin,
219         area: &Area,
220     ) -> Result<(), ProgramError> {
221         let width = area.width as usize;
222         let mut s = format!("{}", self.len);
223         if s.len() > width {
224             return Ok(());
225         }
226         if s.len() + " bytes".len() < width {
227             s = format!("{} bytes", s);
228         } else if s.len() + 1 < width {
229             s = format!("{}b", s);
230         }
231         w.queue(cursor::MoveTo(
232             area.left + area.width - s.len() as u16,
233             area.top,
234         ))?;
235         panel_skin.styles.default.queue(w, s)?;
236         Ok(())
237     }
238 }
239 
is_thumb(y: u16, scrollbar: Option<(u16, u16)>) -> bool240 fn is_thumb(y: u16, scrollbar: Option<(u16, u16)>) -> bool {
241     if let Some((sctop, scbottom)) = scrollbar {
242         if sctop <= y && y <= scbottom {
243             return true;
244         }
245     }
246     false
247 }
248