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(format!("No files found that matches \"{}\"", path).into());
29 }
30 let selected = contents
31 .into_iter()
32 .nth(scroll)
33 .expect("Scroll out of bounds for go_to_path.");
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("A file should have a parent directory.")
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 &format!("File type: {:?}", header.file_type()),
125 ),
126 Header::CustomHeader(_) => self.log(NotificationLevel::Info, "File type: Custom"),
128 Header::None => unreachable!(),
129 }
130 self.log(
131 NotificationLevel::Info,
132 &format!("Architecture: {:?}", self.header.architecture()),
133 );
134 self.log(
135 NotificationLevel::Info,
136 &format!("Bitness: {}", self.header.bitness()),
137 );
138 self.log(
139 NotificationLevel::Info,
140 &format!("Entry point: {:#X}", self.header.entry_point()),
141 );
142 for section in self.header.get_sections() {
143 self.log(NotificationLevel::Info, &format!("Section: {}", section));
144 }
145 } else {
146 self.log(NotificationLevel::Info, "No header found. Assuming 64-bit.");
147 }
148
149 self.log(
150 NotificationLevel::Info,
151 &format!(
152 "Press {} for a list of commands.",
153 Self::key_event_to_string(self.settings.key.help)
154 ),
155 );
156 }
157
158 pub(in crate::app) fn open_file<B: Backend>(
159 &mut self,
160 path: &str,
161 terminal: &mut Terminal<B>,
162 ) -> Result<(), Box<dyn Error>> {
163 self.log(
164 NotificationLevel::Info,
165 &format!("Opening file: \"{}\"", path),
166 );
167
168 self.filesystem.cd(path);
169 self.info_mode = InfoMode::Text;
170 self.scroll = 0;
171 self.cursor = (0, 0);
172
173 self.screen_size = Self::get_size(terminal)?;
174 self.block_size = 8;
175 self.vertical_margin = 2;
176 self.blocks_per_row = Self::calc_blocks_per_row(
177 self.block_size,
178 self.screen_size.0,
179 self.fullscreen,
180 self.selected_pane,
181 );
182
183 Self::print_loading_status(
184 &self.settings.color,
185 &format!("Opening \"{}\"...", path),
186 terminal,
187 )?;
188 self.data = Data::new(
189 self.filesystem.read(self.filesystem.pwd())?,
190 self.settings.app.history_limit,
191 );
192
193 self.load_comments(None);
194
195 Self::print_loading_status(&self.settings.color, "Decoding binary data...", terminal)?;
196
197 self.header = self.parse_header();
198
199 Self::print_loading_status(
200 &self.settings.color,
201 "Disassembling executable...",
202 terminal,
203 )?;
204
205 (self.assembly_offsets, self.assembly_instructions) =
206 Self::sections_from_bytes(self.data.bytes(), &self.header);
207
208 Self::print_loading_status(&self.settings.color, "Opening ui...", terminal)?;
209 self.log_header_info();
210 let mut app_context = get_app_context!(self);
211 self.plugin_manager.on_open(&mut app_context);
212
213 Ok(())
214 }
215
216 pub(in crate::app) fn save_file_as(&mut self, path: &str) -> Result<(), Box<dyn Error>> {
217 if let Some(parent) = path::parent(path) {
218 self.filesystem.mkdirs(parent)?;
219 };
220
221 self.filesystem.create(path)?;
222 self.filesystem.cd(&self.filesystem.canonicalize(path)?);
223 self.save_file()?;
224 Ok(())
225 }
226
227 pub(in crate::app) fn save_file(&mut self) -> Result<(), Box<dyn Error>> {
228 let mut app_context = get_app_context!(self);
229 self.plugin_manager.on_save(&mut app_context);
230 self.filesystem
231 .write(self.filesystem.pwd(), self.data.bytes())?;
232 self.data.reset_dirty();
233 match &self.filesystem {
234 FileSystem::Local { path } => {
235 self.log(NotificationLevel::Info, &format!("Saved to {}", path));
236 }
237 FileSystem::Remote { path, connection } => {
238 self.log(
239 NotificationLevel::Info,
240 &format!("Saved to {} at {}", path, connection),
241 );
242 }
243 }
244
245 Ok(())
246 }
247}