1use std::error::Error;
2
3use ratatui::text::{Line, Span, Text};
4
5use crate::get_app_context;
6
7use super::{
8 asm::assembly_line::AssemblyLine, info_mode::InfoMode, pane::Pane,
9 settings::color_settings::ColorSettings, App,
10};
11
12pub(super) struct InstructionInfo {
13 pub offset: isize,
14 pub length: usize,
15 pub is_section: bool,
16}
17
18impl App {
19 pub(super) fn bytes_to_styled_hex(
20 color_settings: &ColorSettings,
21 bytes: &[u8],
22 block_size: usize,
23 blocks_per_row: usize,
24 selected_byte_index: usize,
25 high_byte: bool,
26 instruction_info: Option<InstructionInfo>,
27 ) -> Text<'static> {
28 let mut ret = Text::default();
29 ret.lines
30 .reserve(bytes.len() / (block_size * blocks_per_row) + 1);
31 let mut current_line = Line::default();
32 let mut local_block = 0;
33 let mut local_byte = 0;
34 for (byte_index, b) in bytes.iter().enumerate() {
35 let byte_index = byte_index as isize;
36 let mut next_line = false;
37 let hex_chars = Self::u8_to_hex(*b);
38 let hex_high = hex_chars[0].to_string();
39 let hex_low = hex_chars[1].to_string();
40 let (mut space_style, mut style) = (
41 color_settings.hex_default,
42 Self::get_style_for_byte(color_settings, *b),
43 );
44
45 if let Some(instruction_info) = &instruction_info {
46 let used_style = if instruction_info.is_section {
47 color_settings.hex_current_section
48 } else {
49 color_settings.hex_current_instruction
50 };
51 if byte_index >= instruction_info.offset
52 && byte_index < instruction_info.offset + instruction_info.length as isize
53 {
54 let is_last_space = byte_index
55 == instruction_info.offset + instruction_info.length as isize - 1;
56 if !is_last_space {
57 space_style = used_style;
58 }
59 style = used_style;
60 }
61 }
62
63 let span = Span::styled(
64 hex_high,
65 if byte_index == selected_byte_index as isize && high_byte {
66 color_settings.hex_selected
67 } else {
68 style
69 },
70 );
71 current_line.spans.push(span);
72 let span = Span::styled(
73 hex_low,
74 if byte_index == selected_byte_index as isize && !high_byte {
75 color_settings.hex_selected
76 } else {
77 style
78 },
79 );
80 current_line.spans.push(span);
81 let mut spacing_string = " ".to_string();
82 local_byte += 1;
83 if local_byte % block_size == 0 {
84 local_byte = 0;
85 spacing_string.push(' ');
86
87 local_block += 1;
88 if local_block % blocks_per_row == 0 {
89 local_block = 0;
90 next_line = true;
91 }
92 }
93
94 let span = Span::styled(spacing_string, space_style);
95 current_line.spans.push(span);
96
97 if next_line {
98 let new_line = std::mem::take(&mut current_line);
99 ret.lines.push(new_line);
100 }
101 }
102 if !current_line.spans.is_empty() {
103 ret.lines.push(current_line);
104 }
105
106 ret
107 }
108
109 pub(super) fn resize_to_size(&mut self, width: u16, height: u16) {
110 let blocks_per_row: usize =
111 Self::calc_blocks_per_row(self.block_size, width, self.fullscreen, self.selected_pane);
112 if (width, height) != self.screen_size || blocks_per_row != self.blocks_per_row {
113 self.screen_size = (width, height);
114 self.resize(blocks_per_row);
115 }
116 }
117
118 pub(super) fn resize(&mut self, blocks_per_row: usize) {
119 let old_cursor = self.get_cursor_position();
120 self.blocks_per_row = blocks_per_row;
121
122 self.jump_to(old_cursor.global_byte_index, false);
123 }
124
125 pub(super) fn calc_blocks_per_row(
126 block_size: usize,
127 width: u16,
128 fullscreen: bool,
129 selected_pane: Pane,
130 ) -> usize {
131 let block_characters_hex = block_size * 3 + 1;
132 let block_characters_text = block_size * 2 + 1;
133 let available_width = width.saturating_sub(18 + 2 + 2);
134 let complessive_chars_per_block = if fullscreen {
135 match selected_pane {
136 Pane::Hex => block_characters_hex,
137 Pane::View => block_characters_text,
138 }
139 } else {
140 block_characters_hex + block_characters_text
141 };
142 let blocks_per_row = (available_width + 2) / complessive_chars_per_block as u16;
143 (blocks_per_row as usize).max(1)
144 }
145
146 pub(super) fn u8_to_hex(input: u8) -> [char; 2] {
147 let symbols = [
148 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
149 ];
150 let low = input & 0x0f;
151 let high = (input & 0xf0) >> 4;
152 [symbols[high as usize], symbols[low as usize]]
153 }
154
155 pub(super) fn edit_data(&mut self, mut value: char) -> Result<(), Box<dyn Error>> {
156 value = value.to_uppercase().next().unwrap();
157
158 if value.is_ascii_hexdigit() {
159 let cursor_position = self.get_cursor_position();
160
161 let old_byte = self.data.bytes()[cursor_position.global_byte_index];
162 let old_byte_str = format!("{old_byte:02X}");
163 let new_byte_str = if cursor_position.high_byte {
164 format!("{}{}", value, old_byte_str.chars().nth(1).unwrap())
165 } else {
166 format!("{}{}", old_byte_str.chars().nth(0).unwrap(), value)
167 };
168 let new_byte = u8::from_str_radix(&new_byte_str, 16).unwrap();
169
170 let mut new_bytes = vec![new_byte];
171 let mut app_context = get_app_context!(self);
172
173 self.plugin_manager
174 .on_edit(&mut new_bytes, &mut app_context);
175
176 let modified_bytes = self
177 .data
178 .push_change(cursor_position.global_byte_index, new_bytes);
179
180 self.edit_assembly(modified_bytes);
181 }
182 Ok(())
183 }
184
185 pub(super) fn get_hex_view(&self, start_row: usize, end_row: usize) -> Text<'static> {
187 let start_byte = start_row * self.blocks_per_row * self.block_size;
188 let end_byte = end_row * self.blocks_per_row * self.block_size;
189 let end_byte = std::cmp::min(end_byte, self.data.len());
190 let bytes = &self.data.bytes()[start_byte..end_byte];
191 let selected_byte_index = self
192 .get_cursor_position()
193 .global_byte_index
194 .saturating_sub(start_byte);
195 let high_byte = self.get_cursor_position().high_byte;
196 let instruction_info = {
197 if self.info_mode == InfoMode::Assembly {
198 let current_instruction = self.get_current_instruction();
199 if let Some(assembly_line) = current_instruction {
200 let offset = assembly_line.file_address() as isize - start_byte as isize;
201 let length = assembly_line.len();
202 let is_section = matches!(assembly_line, AssemblyLine::SectionTag(_));
203 Some(InstructionInfo {
204 offset,
205 length,
206 is_section,
207 })
208 } else {
209 None
210 }
211 } else {
212 None
213 }
214 };
215 Self::bytes_to_styled_hex(
216 &self.settings.color,
217 bytes,
218 self.block_size,
219 self.blocks_per_row,
220 selected_byte_index,
221 high_byte,
222 instruction_info,
223 )
224 }
225}
226
227#[cfg(test)]
228mod test {
229 use super::*;
230
231 #[test]
232 fn test_resize() {
233 let data = vec![0; 0x100];
234 let mut app = App::mockup(data);
235 app.resize_to_size(80, 24);
236 app.resize_to_size(80, 50);
237 app.resize_to_size(40, 24);
238 app.resize_to_size(250, 250);
239
240 app.resize_to_size(80, 1);
241 app.resize_to_size(250, 1);
242 app.resize_to_size(40, 1);
243 app.resize_to_size(1, 1);
244
245 app.resize_to_size(1, 50);
246 app.resize_to_size(1, 250);
247 app.resize_to_size(1, 24);
248 app.resize_to_size(1, 1);
249 app.resize_to_size(80, 24);
250 }
251
252 #[test]
253 fn test_u8_to_hex() {
254 assert_eq!(App::u8_to_hex(0x00), ['0', '0']);
255 assert_eq!(App::u8_to_hex(0x01), ['0', '1']);
256 assert_eq!(App::u8_to_hex(0x0A), ['0', 'A']);
257 assert_eq!(App::u8_to_hex(0x0F), ['0', 'F']);
258 assert_eq!(App::u8_to_hex(0x10), ['1', '0']);
259 assert_eq!(App::u8_to_hex(0x1F), ['1', 'F']);
260 assert_eq!(App::u8_to_hex(0xF0), ['F', '0']);
261 assert_eq!(App::u8_to_hex(0xFF), ['F', 'F']);
262 }
263}