1#![allow(clippy::module_inception)]
2use std::error::Error;
3
4use ratatui::{backend::Backend, Terminal};
5
6use crate::{
7 app::{
8 data::Data, info_mode::InfoMode, log::NotificationLevel, popup::popup_state::PopupState,
9 App,
10 },
11 get_app_context,
12 headers::Header,
13};
14
15use super::{filesystem::FileSystem, path, path_result::PathResult};
16
17impl App {
18 pub(in crate::app) fn go_to_path<B: Backend>(
19 &mut self,
20 currently_open_path: &str,
21 path: &str,
22 scroll: usize,
23 popup: &mut Option<PopupState>,
24 terminal: &mut Terminal<B>,
25 ) -> Result<(), Box<dyn Error>> {
26 let contents = Self::find_dir_contents(currently_open_path, path, &self.filesystem)?;
27 if contents.is_empty() {
28 return Err(t!("app.messages.no_file_match", path = path).into());
29 }
30 let selected = contents
31 .into_iter()
32 .nth(scroll)
33 .expect(&t!("errors.go_to_path_scroll_out_of_bounds"));
34
35 if self.filesystem.is_dir(selected.path()) {
36 Self::open_dir(popup, selected.path(), &mut self.filesystem)?;
37 } else {
38 self.open_file(selected.path(), terminal)?;
39 *popup = None;
40 }
41
42 Ok(())
43 }
44
45 pub(in crate::app) fn get_current_dir(&self) -> String {
46 let current_path = self.filesystem.pwd();
47 if self.filesystem.is_dir(current_path) {
48 current_path.to_owned()
49 } else {
50 path::parent(current_path)
51 .expect(&t!("errors.file_no_parent"))
52 .to_owned()
53 }
54 }
55
56 pub(in crate::app) fn find_dir_contents(
57 currently_open_path: &str,
58 path: &str,
59 filesystem: &FileSystem,
60 ) -> Result<Vec<PathResult>, Box<dyn Error>> {
61 let mut ret = Vec::new();
62 let (selected_dir, file_name) = if path::is_absolute(path) {
63 if filesystem.is_dir(path) {
64 (filesystem.canonicalize(path)?, "".to_string())
65 } else if let Some(parent) = path::parent(path) {
66 if filesystem.is_dir(parent) {
67 (
68 filesystem.canonicalize(parent)?,
69 path::filename(path).map_or("".into(), |name| name.to_string()),
70 )
71 } else {
72 (currently_open_path.to_string(), path.to_string())
73 }
74 } else {
75 (currently_open_path.to_string(), path.to_string())
76 }
77 } else {
78 (currently_open_path.to_string(), path.to_string())
79 };
80
81 let entries = filesystem.ls(&selected_dir)?;
82 let entries = entries
83 .into_iter()
84 .map(|entry| path::diff(&entry, &selected_dir).to_string())
85 .collect::<Vec<_>>();
86
87 let entries = entries
88 .into_iter()
89 .filter(|entry| entry.to_lowercase().starts_with(&file_name.to_lowercase()));
90
91 for entry in entries {
92 if let Ok(result) = PathResult::new(
93 &path::join(&selected_dir, &entry, filesystem.separator()),
94 filesystem,
95 ) {
96 ret.push(result);
97 }
98 }
99
100 Ok(ret)
101 }
102
103 pub(in crate::app) fn open_dir(
104 popup: &mut Option<PopupState>,
105 path: &str,
106 filesystem: &mut FileSystem,
107 ) -> Result<(), Box<dyn Error>> {
108 let path = filesystem.canonicalize(path)?;
109 *popup = Some(PopupState::Open {
110 currently_open_path: path.clone(),
111 path: "".into(),
112 cursor: 0,
113 results: Self::find_dir_contents(&path, "", filesystem)?,
114 scroll: 0,
115 });
116 Ok(())
117 }
118
119 pub fn log_header_info(&mut self) {
120 if self.header != Header::None {
121 match &self.header {
122 Header::GenericHeader(header) => self.log(
123 NotificationLevel::Info,
124 t!("app.messages.file_type", file_type = header.file_type() : {:?}),
125 ),
126 Header::CustomHeader(_) => self.log(
128 NotificationLevel::Info,
129 t!("app.messages.file_type_custom",),
130 ),
131 Header::None => unreachable!(),
132 }
133 self.log(
134 NotificationLevel::Info,
135 t!("app.messages.architecture", architecture = self.header.architecture() : {:?}),
136 );
137 self.log(
138 NotificationLevel::Info,
139 t!("app.messages.bitness", bitness = self.header.bitness()),
140 );
141 self.log(
142 NotificationLevel::Info,
143 t!("app.messages.entry_point", entry_point = self.header.entry_point() : {:#X}),
144 );
145 for section in self.header.get_sections() {
146 self.log(
147 NotificationLevel::Info,
148 t!("app.messages.section", section = section),
149 );
150 }
151 } else {
152 self.log(NotificationLevel::Info, t!("app.messages.no_header"));
153 }
154
155 self.log(
156 NotificationLevel::Info,
157 t!(
158 "app.messages.press_for_help",
159 key = Self::key_event_to_string(self.settings.key.help)
160 ),
161 );
162 }
163
164 pub(in crate::app) fn open_file<B: Backend>(
165 &mut self,
166 path: &str,
167 terminal: &mut Terminal<B>,
168 ) -> Result<(), Box<dyn Error>> {
169 self.log(
170 NotificationLevel::Info,
171 t!("app.messages.opening_file", path = path),
172 );
173
174 self.filesystem.cd(path);
175 self.info_mode = InfoMode::Text;
176 self.scroll = 0;
177 self.cursor = (0, 0);
178
179 self.screen_size = Self::get_size(terminal)?;
180 self.block_size = 8;
181 self.vertical_margin = 2;
182 self.blocks_per_row = Self::calc_blocks_per_row(
183 self.block_size,
184 self.screen_size.0,
185 self.fullscreen,
186 self.selected_pane,
187 );
188
189 Self::print_loading_status(
190 &self.settings.color,
191 &t!("app.messages.opening_path", path = path),
192 terminal,
193 )?;
194 self.data = Data::new(
195 self.filesystem.read(self.filesystem.pwd())?,
196 self.settings.app.history_limit,
197 );
198
199 self.load_comments(None);
200
201 Self::print_loading_status(
202 &self.settings.color,
203 &t!("app.messages.decoding_binary"),
204 terminal,
205 )?;
206
207 self.header = self.parse_header();
208
209 Self::print_loading_status(
210 &self.settings.color,
211 &t!("app.messages.disassembling_executable"),
212 terminal,
213 )?;
214
215 (self.assembly_offsets, self.assembly_instructions) =
216 Self::sections_from_bytes(self.data.bytes(), &self.header);
217
218 Self::print_loading_status(
219 &self.settings.color,
220 &t!("app.messages.opening_ui"),
221 terminal,
222 )?;
223 self.log_header_info();
224 let mut app_context = get_app_context!(self);
225 self.plugin_manager.on_open(&mut app_context);
226
227 Ok(())
228 }
229
230 pub(in crate::app) fn save_file_as(&mut self, path: &str) -> Result<(), Box<dyn Error>> {
231 if let Some(parent) = path::parent(path) {
232 self.filesystem.mkdirs(parent)?;
233 };
234
235 self.filesystem.create(path)?;
236 self.filesystem.cd(&self.filesystem.canonicalize(path)?);
237 self.save_file()?;
238 Ok(())
239 }
240
241 pub(in crate::app) fn save_file(&mut self) -> Result<(), Box<dyn Error>> {
242 let mut app_context = get_app_context!(self);
243 self.plugin_manager.on_save(&mut app_context);
244 self.filesystem
245 .write(self.filesystem.pwd(), self.data.bytes())?;
246 self.data.reset_dirty();
247 match &self.filesystem {
248 FileSystem::Local { path } => {
249 self.log(
250 NotificationLevel::Info,
251 t!("app.messages.saved_to", path = path),
252 );
253 }
254 FileSystem::Remote { path, connection } => {
255 self.log(
256 NotificationLevel::Info,
257 t!("app.messages.saved_to_ssh", path = path, ssh = connection),
258 );
259 }
260 }
261
262 Ok(())
263 }
264}