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