1use ratatui::text::{Line, Span, Text};
2
3use super::{log::NotificationLevel, settings::color_settings::ColorSettings, App};
4
5impl App {
6 pub(super) fn bytes_to_styled_text(
7 color_settings: &ColorSettings,
8 bytes: &'_ [u8],
9 block_size: usize,
10 blocks_per_row: usize,
11 selected_byte_offset: usize,
12 ) -> Text<'static> {
13 let mut ret = Text::default();
14 ret.lines
15 .reserve(bytes.len() / (block_size * blocks_per_row) + 1);
16 let mut current_line = Line::default();
17 let mut local_block = 0;
18 let mut local_byte = 0;
19 for (byte_index, b) in bytes.iter().enumerate() {
20 let style = if byte_index == selected_byte_offset {
21 color_settings.text_selected
22 } else {
23 Self::get_style_for_byte(color_settings, *b)
24 };
25 let mut next_line = false;
26 let char = Self::u8_to_char(*b);
27 let char_string = char.to_string();
28 let span = Span::styled(char_string, style);
29 current_line.spans.push(span);
30 let mut spacing_string = " ".to_string();
31 local_byte += 1;
32 if local_byte % block_size == 0 {
33 local_byte = 0;
34 spacing_string.push(' ');
35
36 local_block += 1;
37 if local_block % blocks_per_row == 0 {
38 local_block = 0;
39 next_line = true;
40 }
41 }
42
43 let span = Span::raw(spacing_string);
44 current_line.spans.push(span);
45
46 if next_line {
47 let new_line = std::mem::take(&mut current_line);
48 ret.lines.push(new_line);
49 }
50 }
51 if !current_line.spans.is_empty() {
52 ret.lines.push(current_line);
53 }
54
55 ret
56 }
57
58 pub(super) fn insert_text(&mut self, text: &str) {
59 self.patch_bytes(text.as_bytes(), false);
60 }
61
62 fn found_text_here(&self, starting_from: usize, text: &str) -> bool {
63 for (i, byte) in text.bytes().enumerate() {
64 if self.data.len() <= starting_from + i || self.data.bytes()[starting_from + i] != byte
65 {
66 return false;
67 }
68 }
69 true
70 }
71
72 pub(super) fn get_text_view(&self, start_row: usize, end_row: usize) -> Text<'static> {
73 let start_byte = start_row * self.blocks_per_row * self.block_size;
74 let end_byte = end_row * self.blocks_per_row * self.block_size;
75 let end_byte = std::cmp::min(end_byte, self.data.len());
76 let bytes = &self.data.bytes()[start_byte..end_byte];
77 let selected_byte_offset = self
78 .get_cursor_position()
79 .global_byte_index
80 .saturating_sub(start_byte);
81 Self::bytes_to_styled_text(
82 &self.settings.color,
83 bytes,
84 self.block_size,
85 self.blocks_per_row,
86 selected_byte_offset,
87 )
88 }
89
90 pub(super) fn find_text(&mut self, text: &str) {
91 if text.is_empty() || self.data.is_empty() {
92 return;
93 }
94 let already_searched = self.text_last_searched_string == text;
95 if !already_searched {
96 self.text_last_searched_string = text.to_string();
97 }
98 let mut search_here = self.get_cursor_position().global_byte_index;
99 if already_searched && Self::found_text_here(self, search_here, text) {
101 search_here += text.len();
102 } else {
103 search_here = 0;
104 }
105 let max_search_index = self.data.len() + search_here;
106 while search_here < max_search_index {
107 let actual_search_here = search_here % self.data.len();
108 if Self::found_text_here(self, actual_search_here, text) {
109 self.jump_to(actual_search_here, false);
110 return;
111 }
112 search_here += 1;
113 }
114 self.log(
115 NotificationLevel::Warning,
116 t!("app.messages.text_not_found"),
117 );
118 }
119
120 pub(super) fn u8_to_char(input: u8) -> char {
121 match input {
122 0x20..=0x7E => input as char,
123 0x0A => '⏎',
124 0x0C => '↡',
125 0x0D => '↵',
126 0x08 => '⇤',
127 0x09 => '⇥',
128 0x1B => '␛',
129 0x7F => '␡',
130 _ => '.',
131 }
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_bytes_to_styled_text() {
141 let color_settings = ColorSettings::get_default_dark_theme();
142 let bytes = b"CAFEBABE";
143 let block_size = 8;
144 let blocks_per_row = 2;
145 let selected_byte_offset = 0;
146 let text = App::bytes_to_styled_text(
147 &color_settings,
148 bytes,
149 block_size,
150 blocks_per_row,
151 selected_byte_offset,
152 );
153 assert_eq!(text.lines.len(), 1);
154 let mut char_index = 0;
155 for char in text.lines[0]
156 .spans
157 .iter()
158 .flat_map(|span| span.content.chars())
159 {
160 if char.is_alphanumeric() {
161 assert_eq!(char, bytes[char_index] as char);
162 char_index += 1;
163 }
164 }
165 assert_eq!(char_index, bytes.len());
166 }
167}