1use ansi_term::Color;
2use ec::State;
3use regex::Regex;
4use std::collections::HashSet;
5use std::io;
6use std::io::Write;
7use std::num::NonZeroUsize;
8use std::path::PathBuf;
9
10extern crate edhex_core as ec;
16
17
18macro_rules! skip_bad_range {
19 ($command:expr, $all_bytes:expr) => {
20 if $command.bad_range(&$all_bytes) {
21 println!("? (bad range)");
22 continue;
23 }
24 };
25}
26
27static DEFAULT_BEFORE_CONTEXT:usize = 10;
28static DEFAULT_AFTER_CONTEXT:usize= 10;
29
30
31#[derive(Debug)]
32struct Command {
33 range: (usize, usize),
34 command: char,
35 args: Vec<String>,
36}
37
38
39fn print_help(state:&State) {
40 print!("Input/output is hex unless toggled to decimal with 'x'
41h This (h)elp
42<Enter> Print current byte(s) and move forward to next line of byte(s)
43j (j)ump back to previous line of byte(s) and print
443d4 Move to byte number 3d4 and print from there
45+ Move 1 byte forward and print from there
46+++ Move 3 bytes forward and print from there
47- Move 1 byte back and print from there
48+3d4 Move 3d4 bytes forward and print from there
49-3d4 Move 3d4 bytes back and print from there
50$ Move to last byte and print it
51/deadbeef If bytes de ad be ef exist after current index, move there and print
52?deadbeef If bytes de ad be ef exist before current index, move there and print
53/ Perform last search again starting at next byte
54? Perform last search (backwards) again starting at previous byte
55k Delete/(k)ill byte at current index and print new line of byte(s)
567dk Move to byte 7d, (k)ill that byte, and print from there.
571d,72k Move to byte 1d; (k)ill bytes 1d - 72 inclusive; print from there
58/deadbeef/k If bytes de ad be ef exist after current index, move there,
59 (k)ill those bytes, and print
60i Prompt you to enter bytes which will be (i)nserted at current index
6172i Move to byte number 72; prompt you to enter bytes to (i)nsert there
62/deadbeef/i If bytes de ad be ef exist after current index, move there
63 and prompt you to enter bytes which will be (i)nserted there
6412,3dp (p)rint bytes 12 - 3d inclusive, move to byte 12
65l (l)oad a new file.
66L (L)oad state from a file. Fails if file you were editing is gone.
67m Toggle whether or not characters are printed after bytes
68n Toggle whether or not byte (n)umbers are printed before bytes
69o Toggle using c(o)lor
70p (p)rint current line of byte(s) (depending on 'W')
71P Save (P)references to file (width, color, etc.)
72r (r)ead preferences from a file.
73R Toggle (R)ead-only mode
74s Print (s)tate of toggles, 'W'idth, etc.
75S (S)ave state to a file except the bytes you're editing.
76t3d Print 0x3d lines of con(t)extual bytes after current line [Default {}]
77T3d Print 0x3d lines of con(T)extual bytes before current line [Default {}]
78u (u)pdate filename to write to
79U Toggle (U)nderlining main line
80v Insert a (v)isual break in the display at the current byte.
81 NOTE: This does not insert a byte in the file. It's just display.
82V Remove a (V)isual break if one is at the current byte.
83x Toggle reading input and displaying output as he(x) or decimal
84w Actually (w)rite changes to the file on disk
85W3d Set (W)idth to 0x3d. i.e. print a linebreak every 3d bytes [Default {}]
86q (q)uit
87",
88 ec::hex_unless_dec_with_radix(DEFAULT_BEFORE_CONTEXT, state.prefs.radix),
89 ec::hex_unless_dec_with_radix(DEFAULT_AFTER_CONTEXT, state.prefs.radix),
90 ec::hex_unless_dec_with_radix(ec::DEFAULT_WIDTH, state.prefs.radix)
91);
92}
93
94fn read_bytes_from_user() -> Result<Vec<u8>, String> {
95 print!("> ");
96 io::stdout().flush().unwrap();
97 let input = match ec::get_input_or_die() {
98 Ok(input) => input,
99 Err(_errcode) => {
100 return Err("Couldn't read input".to_owned());
101 }
102 };
103
104 ec::bytes_from_string(&input)
105}
106
107
108impl Command {
109 fn bad_range(&self, all_bytes: &Vec<u8>) -> bool {
110 ec::bad_range(all_bytes, self.range)
111 }
112
113
114 fn from_state_and_line(state:&mut State, line: &str) -> Result<Command, String> {
115 let re_blank_line = Regex::new(r"^ *$").unwrap();
119 let re_pluses = Regex::new(r"^ *(?P<pluses>\++) *$").unwrap();
120 let re_minuses = Regex::new(r"^ *(?P<minuses>\-+) *$").unwrap();
121 let re_search = Regex::new(r"^ *(?P<direction>[/?]) *(?P<bytes>[0-9a-fA-F]+) *$").unwrap();
122 let re_search_again = Regex::new(r"^ *(?P<direction>[/?]) *$").unwrap();
123 let re_search_kill = Regex::new(r"^ */(?P<bytes>[0-9a-fA-F]+)/k *$").unwrap();
124 let re_search_insert = Regex::new(r"^ */(?P<bytes>[0-9a-fA-F]+)/i *$").unwrap();
125 let re_single_char_command = Regex::new(r"^ *(?P<command>[hijkmnopqRrsSlLPuUvVwx]).*$").unwrap();
126 let re_range = Regex::new(r"^ *(?P<begin>[0-9a-fA-F.$]+) *, *(?P<end>[0-9a-fA-F.$]+) *(?P<the_rest>.*) *$").unwrap();
127 let re_specified_index = Regex::new(r"^ *(?P<index>[0-9A-Fa-f.$]+) *(?P<the_rest>.*) *$").unwrap();
128 let re_offset_index = Regex::new(r"^ *(?P<sign>[-+])(?P<offset>[0-9A-Fa-f]+) *(?P<the_rest>.*) *$").unwrap();
129 let re_matches_nothing = Regex::new(r"^a\bc").unwrap();
130 let re_width = Regex::new(r"^ *W *(?P<width>[0-9A-Fa-f]+) *$").unwrap();
131 let re_before_context = Regex::new(r"^ *T *(?P<before_context>[0-9A-Fa-f]+) *$").unwrap();
132 let re_after_context = Regex::new(r"^ *t *(?P<after_context>[0-9A-Fa-f]+) *$").unwrap();
133
134 let is_blank_line = re_blank_line.is_match(line);
135 let is_single_char_command = re_single_char_command.is_match(line);
136 let is_pluses = re_pluses.is_match(line);
137 let is_minuses = re_minuses.is_match(line);
138 let is_range = re_range.is_match(line);
139 let is_search = re_search.is_match(line);
140 let is_search_again = re_search_again.is_match(line);
141 let is_search_kill = re_search_kill.is_match(line);
142 let is_search_insert = re_search_insert.is_match(line);
143 let is_specified_index = re_specified_index.is_match(line);
144 let is_offset_index = re_offset_index.is_match(line);
145 let is_width = re_width.is_match(line);
146 let is_before_context = re_before_context.is_match(line);
147 let is_after_context = re_after_context.is_match(line);
148
149 let re = if is_blank_line {
150 re_blank_line
151 }
152 else if is_single_char_command {
153 re_single_char_command
154 }
155 else if is_search {
156 re_search
157 }
158 else if is_search_again {
159 re_search_again
160 }
161 else if is_search_insert {
162 re_search_insert
163 }
164 else if is_search_kill {
165 re_search_kill
166 }
167 else if is_pluses {
168 re_pluses
169 }
170 else if is_minuses {
171 re_minuses
172 }
173 else if is_range {
174 re_range
175 }
176 else if is_specified_index {
177 re_specified_index
178 }
179 else if is_offset_index {
180 re_offset_index
181 }
182 else if is_before_context {
183 re_before_context
184 }
185 else if is_after_context {
186 re_after_context
187 }
188 else if is_width {
189 re_width
190 }
191 else {
192 re_matches_nothing
193 };
194
195 let caps = re.captures(line);
196
197 if is_pluses {
198 let num_pluses = ec::num_graphemes(caps.unwrap().name("pluses").unwrap().as_str());
199 Ok(Command{
200 range: (num_pluses, num_pluses),
201 command: 'G',
202 args: vec![],
203 })
204 }
205
206 else if is_search_insert {
207 match ec::bytes_from_string(caps.unwrap().name("bytes").unwrap().as_str()) {
208 Ok(needle) => {
209 if let Some(offset) = ec::index_of_bytes(&needle, &state.all_bytes[state.index..], true) {
210 Ok(Command{
211 range: (state.index + offset, state.index + offset),
212 command: 'i',
213 args: vec![],
214 })
215 }
216 else {
217 Err(format!("{} not found", ec::string_from_bytes(&needle)))
218 }
219 },
220 Err(error) => {
221 Err(error)
222 }
223 }
224 }
225
226 else if is_search_kill {
227 match ec::bytes_from_string(caps.unwrap().name("bytes").unwrap().as_str()) {
228 Ok(needle) => {
229 let needle_num_bytes = if needle.len() == 0 {
230 return Err("Searching for empty string".to_owned());
231 }
232 else {
233 needle.len()
234 };
235
236 if let Some(offset) = ec::index_of_bytes(&needle, &state.all_bytes[state.index..], true) {
237 Ok(Command{
238 range: (state.index + offset, state.index + offset + needle_num_bytes - 1),
239 command: 'k',
240 args: vec![],
241 })
242 }
243 else {
244 Err(format!("{} not found", ec::string_from_bytes(&needle)))
245 }
246 },
247 Err(error) => {
248 Err(error)
249 }
250 }
251 }
252
253 else if is_search_again {
254 if state.last_search.is_none() {
255 return Err(format!("No previous search."));
256 }
257
258 let needle = state.last_search.to_owned().unwrap();
259
260 let caps = caps.unwrap();
261 let forward = caps.name("direction").unwrap().as_str() == "/";
262
263 let haystack = if forward {
265 &state.all_bytes[(state.index + 1)..]
266 }
267 else {
268 &state.all_bytes[..(state.index.saturating_sub(1))]
269 };
270
271 if let Some(offset) = ec::index_of_bytes(&needle, haystack, forward) {
272 if forward {
273 Ok(Command{
274 range: (state.index + 1 + offset, state.index + 1 + offset),
275 command: 'g',
276 args: vec![],
277 })
278 }
279 else {
280 Ok(Command{
281 range: (offset, offset),
282 command: 'g',
283 args: vec![],
284 })
285 }
286 }
287 else {
288 Err(format!("{} not found", ec::string_from_bytes(&needle)))
289 }
290 }
291
292 else if is_search {
293 let caps = caps.unwrap();
294 let forward = caps.name("direction").unwrap().as_str() == "/";
295 match ec::bytes_from_string(caps.name("bytes").unwrap().as_str()) {
296 Ok(needle) => {
297 state.last_search = Some(needle.to_owned());
298
299 let haystack = if forward {
300 &state.all_bytes[state.index..]
301 }
302 else {
303 &state.all_bytes[..state.index]
304 };
305 if let Some(offset) = ec::index_of_bytes(&needle, haystack, forward) {
306 if forward {
307 Ok(Command{
308 range: (state.index + offset, state.index + offset),
309 command: 'g',
310 args: vec![],
311 })
312 }
313 else {
314 Ok(Command{
315 range: (offset, offset),
316 command: 'g',
317 args: vec![],
318 })
319 }
320 }
321 else {
322 Err(format!("{} not found", ec::string_from_bytes(&needle)))
323 }
324 },
325 Err(error) => {
326 Err(error)
327 }
328 }
329 }
330
331 else if is_minuses {
332 let num_minuses = ec::num_graphemes(caps.unwrap().name("minuses").unwrap().as_str());
333 Ok(Command{
334 range: (num_minuses, num_minuses),
335 command: 'H',
336 args: vec![],
337 })
338 }
339
340 else if is_single_char_command {
341 let command = caps.unwrap().name("command").unwrap().as_str().chars().next().unwrap();
342 if command == 'p' {
343 Ok(Command{
344 range: (state.index, state.index),
345 command: 'Q',
346 args: vec![],
347 })
348 }
349 else {
350 Ok(Command{
351 range: (state.index, state.index),
352 command: command,
353 args: vec![],
354 })
355 }
356 }
357
358 else if is_blank_line {
359 Ok(Command{
360 range: (state.index, state.index),
361 command: '\n',
362 args: vec![],
363 })
364 }
365
366 else if is_before_context {
367 let caps = caps.unwrap();
368 let given = caps.name("before_context").unwrap().as_str();
369 if let Ok(before_context) = usize::from_str_radix(given, state.prefs.radix) {
370 Ok(Command{
371 range: (usize::from(before_context), usize::from(before_context)),
372 command: 'T',
373 args: vec![],
374 })
375 }
376 else {
377 Err(format!("Can't interpret {} as a number", given))
378 }
379 }
380
381 else if is_after_context {
382 let caps = caps.unwrap();
383 let given = caps.name("after_context").unwrap().as_str();
384 if let Ok(after_context) = usize::from_str_radix(given, state.prefs.radix) {
385 Ok(Command{
386 range: (usize::from(after_context), usize::from(after_context)),
387 command: 't',
388 args: vec![],
389 })
390 }
391 else {
392 Err(format!("Can't interpret {} as a number", given))
393 }
394 }
395
396 else if is_width {
397 let caps = caps.unwrap();
399 if let Some(width) = NonZeroUsize::new(usize::from_str_radix(caps.name("width").unwrap().as_str(), state.prefs.radix).unwrap()) {
400 Ok(Command{
401 range: (usize::from(width), usize::from(width)),
402 command: 'W',
403 args: vec![],
404 })
405 }
406 else {
407 Err("Width must be positive".to_owned())
408 }
409 }
410
411 else if is_range {
412 if state.empty() {
413 return Err("Empty file".to_owned());
414 }
415
416 let _max_index = match state.max_index() {
417 Ok(max) => max,
418 Err(error) => {
419 return Err(format!("? ({})", error));
420 },
421 };
422
423 let caps = caps.unwrap();
425 let begin = number_dot_dollar(state.index, _max_index,
426 caps.name("begin").unwrap().as_str(), state.prefs.radix);
427 if begin.is_err() {
428 return Err("Can't understand beginning of range.".to_owned());
431 }
432 let begin = begin.unwrap();
433 let end = number_dot_dollar(state.index, _max_index,
434 caps.name("end").unwrap().as_str(), state.prefs.radix);
435 if end.is_err() {
436 return Err("Can't understand end of range.".to_owned());
439 }
440 let end = end.unwrap();
441
442 let the_rest = caps.name("the_rest").unwrap().as_str().trim();
443 if the_rest.len() == 0 {
444 Err("No arguments given".to_owned())
445 }
446 else {
447 Ok(Command{
448 range: (begin, end),
449 command: the_rest.chars().next().unwrap(),
450 args: the_rest[1..].split_whitespace().map(|x| x.to_owned()).collect(),
451 })
452 }
453 }
454
455 else if is_specified_index {
456 if state.empty() {
457 return Err("Empty file".to_owned());
458 }
459
460 let _max_index = match state.max_index() {
461 Ok(max) => max,
462 Err(error) => {
463 return Err(format!("? ({})", error));
464 },
465 };
466
467 let caps = caps.unwrap();
469 let specific_index = number_dot_dollar(state.index, _max_index,
470 caps.name("index").unwrap().as_str(), state.prefs.radix);
471 if specific_index.is_err() {
472 return Err("Can't understand index.".to_owned());
475 }
476 let specific_index = specific_index.unwrap();
477 let the_rest = caps.name("the_rest").unwrap().as_str().trim().to_owned();
478 if the_rest.len() == 0 {
479 Ok(Command{
480 range: (specific_index, specific_index),
481 command: 'g',
482 args: vec![],
483 })
484 }
485 else {
486 let command = the_rest.chars().next().unwrap();
487 let args = the_rest[1..].split_whitespace()
488 .map(|x| x.to_owned()).collect();
489 Ok(Command{
490 range: (specific_index, specific_index),
491 command:
492 if command == 'p' {
493 '☃'
494 }
495 else {
496 command
497 },
498 args: args,
499 })
500 }
501 }
502
503 else if is_offset_index {
504 let caps = caps.unwrap();
506 let as_string = caps.name("offset").unwrap().as_str();
507 let index_offset = usize::from_str_radix(as_string, state.prefs.radix);
508 if index_offset.is_err() {
509 return Err(format!("{} is not a number", as_string));
510 }
511 let index_offset = index_offset.unwrap();
512 let sign = caps.name("sign").unwrap().as_str();
513 let begin = match sign {
514 "+" => state.index + index_offset,
515 "-" => state.index - index_offset,
516 _ => {
517 return Err(format!("Unknown sign {}", sign));
518 }
519 };
520 let range = (begin, begin);
521 let the_rest = caps.name("the_rest").unwrap().as_str();
522 if the_rest.len() == 0 {
523 Ok(Command{
524 range: range,
525 command: 'g',
526 args: vec![],
527 })
528 }
529 else {
530 Ok(Command{
531 range: range,
532 command: the_rest.chars().next().unwrap(),
533 args: the_rest[1..].split_whitespace().map(|x| x.to_owned()).collect(),
534 })
535 }
536 }
537
538 else {
539 Err(format!("Unable to parse '{}'", line.trim()))
540 }
541 }
542}
543
544
545fn number_dot_dollar(index:usize, _max_index:usize, input:&str, radix:u32)
546 -> Result<usize, String> {
547 match input {
548 "$" => Ok(_max_index),
549 "." => Ok(index),
550 something_else => {
551 if let Ok(number) = usize::from_str_radix(input, radix) {
552 Ok(number)
553 }
554 else {
555 return Err(format!("{} isn't a number in base {}", something_else, radix));
556 }
557 }
558 }
559}
560
561
562fn minuses(state:&mut State, num_minuses:usize) -> Result<usize, String> {
564 if state.empty() {
565 Err("Empty file".to_owned())
566 }
567 else if state.index == 0 {
568 Err("already at 0th byte".to_owned())
569 }
570 else if state.index < num_minuses {
571 Err(format!("Going back {} bytes would take you beyond the 0th byte", num_minuses))
572 }
573 else {
574 state.index -= num_minuses;
575 state.print_bytes();
576 Ok(state.index)
577 }
578}
579
580fn pluses(state:&mut State, num_pluses:usize) -> Result<usize, String> {
582 if state.empty() {
583 Err("Empty file".to_owned())
584 }
585 else {
586 match state.max_index() {
587 Ok(max) => {
588 if state.index == max {
589 Err("already at last byte".to_owned())
590 }
591 else if state.index + num_pluses > max {
592 Err(format!("Moving {} bytes would put you past last byte", num_pluses))
593 }
594 else {
595 state.index += num_pluses;
596 state.print_bytes();
597 Ok(state.index)
598 }
599 },
600 Err(error) => {
601 Err(error)
602 },
603 }
604 }
605}
606
607
608pub fn cargo_version() -> Result<String, String> {
609 if let Some(version) = option_env!("CARGO_PKG_VERSION") {
610 return Ok(String::from(version));
611 }
612 return Err("Version unknown (not compiled with cargo)".to_string());
613}
614
615
616pub fn update_filename(state: &mut ec::State) {
617 let filename = ec::read_string_from_user(Some("Enter new filename: "));
618 if filename.is_err() {
619 println!("? {:?}", filename);
620 return;
621 }
622 let filename = filename.unwrap();
623
624 state.filename = filename;
625
626 state.unsaved_changes = true;
628}
629
630
631pub fn load_state_from_file(state: &mut ec::State) {
632 let filename = ec::read_string_from_user(Some(
633 "Enter filename from which to load state: "));
634 if filename.is_ok() {
635 match State::read_from_filename(&filename.unwrap()) {
636 Ok(new_state) => {
637 *state = new_state;
638 },
639 Err(err) => {
640 println!("? {}", err);
641 }
642 }
643 }
644 else {
645 println!("? {:?}", filename);
646 }
647}
648
649
650pub fn load_new_file(state: &mut ec::State) {
651 if state.unsaved_changes {
652 let unsaved_prompt = "You have unsaved changes. Carry on? (y/n): ";
653 println!("{}", unsaved_prompt);
654 let yeses = vec!["y", "Y", "Yes", "yes"];
655 let nos = vec!["n", "N", "No", "no"];
656 let carry_on = loop {
657 let carry_on_s = ec::read_string_from_user(Some(""));
658 if carry_on_s.is_err() {
659 println!("? {:?}", carry_on_s);
660 return;
661 }
662 let carry_on_s = carry_on_s.unwrap();
663
664 if yeses.contains(&carry_on_s.as_str()) {
665 break true;
666 }
667 if nos.contains(&carry_on_s.as_str()) {
668 break false;
669 }
670 println!("{}", unsaved_prompt);
671 };
672 if !carry_on {
673 return;
674 }
675 }
676
677 let filename = ec::read_string_from_user(Some(
678 "Enter filename from which to load bytes: "));
679 if filename.is_err() {
680 println!("? {:?}", filename);
681 return;
682 }
683 let filename = filename.unwrap();
684
685 let maybe_all_bytes =
686 ec::all_bytes_from_filename(&filename);
687 if maybe_all_bytes.is_ok() {
688 state.filename = filename;
689 state.all_bytes = maybe_all_bytes.unwrap();
690 return;
691 }
692
693 match maybe_all_bytes {
694 Err(ec::AllBytesFromFilenameError::NotARegularFile) => {
695 println!("? {} is not a regular file", filename);
696 },
697 Err(ec::AllBytesFromFilenameError::FileDoesNotExist) => {
698 println!("? {} does not exist", filename);
699 println!("Use 'u' to just change filename");
700 },
701 _ => {
702 println!("? {:?}", maybe_all_bytes);
703 },
704 }
705}
706
707
708pub fn load_prefs(state: &mut ec::State) {
709 let pref_path = ec::preferences_file_path();
710 let filename = ec::read_string_from_user(Some(&format!(
711 "Enter filename from which to load preferences [{}]: ",
712 pref_path.display())));
713 if filename.is_ok() {
714 let mut filename = filename.unwrap();
715 if filename == "" {
716 if let Some(pref_path_s) = pref_path.to_str() {
717 filename = pref_path_s.to_owned();
718 }
719 else {
720 println!("? Default path ({}) is not valid unicode.",
721 pref_path.display());
722 return;
723 }
724 }
725
726 let result = ec::Preferences::read_from_filename(&filename);
727 if result.is_ok() {
728 state.prefs = result.unwrap();
729 }
730 else {
731 println!("? {:?}", result);
732 }
733 }
734 else {
735 println!("? {:?}", filename);
736 }
737}
738
739
740pub fn write_out(state: &mut ec::State) {
741 if state.readonly {
742 println!("? Read-only mode");
743 return;
744 }
745
746 if state.filename != "" {
748 let result = std::fs::write(&state.filename, &state.all_bytes);
749 if result.is_err() {
750 println!("? (Couldn't write to {})", state.filename);
751 return;
752 }
753 }
754 else {
755 let filename = ec::read_string_from_user(Some("Enter filename: "));
756 if filename.is_err() {
757 println!("? {:?}", filename);
758 return;
759 }
760 let filename = filename.unwrap();
761
762 let result = std::fs::write(&filename, &state.all_bytes);
764 if result.is_err() {
765 println!("? (Couldn't write to given filename '{}')", filename);
766 return;
767 }
768
769 state.filename = filename;
770 println!("Write successfull, changing filename to '{}'", state.filename);
771 }
772
773 state.unsaved_changes = false;
774}
775
776
777pub fn actual_runtime(filename:&str, pipe_mode:bool, color:bool, readonly:bool,
779 prefs_path: PathBuf, state_path: PathBuf) -> i32 {
780 let default_prefs = ec::Preferences {
781 show_prompt: !pipe_mode,
782 color: color,
783 before_context: if pipe_mode {0} else {DEFAULT_BEFORE_CONTEXT},
784 after_context: if pipe_mode {0} else {DEFAULT_AFTER_CONTEXT},
785 ..ec::Preferences::default()
786 };
787
788 let maybe_state = ec::State::read_from_path(&state_path);
790 let mut state = if maybe_state.is_ok() {
791 maybe_state.unwrap()
792 }
793 else {
794 ec::State {
795 prefs: default_prefs,
796 unsaved_changes: (filename == ""),
797 filename: filename.to_owned(),
798 readonly: readonly,
799 index: 0,
800 breaks: HashSet::new(),
801 all_bytes: if filename == "" {
802 Vec::new()
803 }
804 else {
805 let maybe_all_bytes = ec::all_bytes_from_filename(filename);
806 if maybe_all_bytes.is_ok() {
807 maybe_all_bytes.unwrap()
808 }
809 else {
810 match maybe_all_bytes {
811 Err(ec::AllBytesFromFilenameError::NotARegularFile) => {
812 println!("{} is not a regular file", filename);
813 return 1;
814 },
815 Err(ec::AllBytesFromFilenameError::FileDoesNotExist) => {
816 Vec::new()
817 },
818 _ => {
819 println!("Cannot read {}", filename);
820 return 1;
821 }
822 }
823 }
824 },
825 last_search: None,
826 }
827 };
828
829 if let Ok(prefs) = ec::Preferences::read_from_path(&prefs_path) {
831 state.prefs = prefs;
832 }
833
834 if !pipe_mode {
835 println!("{}", Color::Yellow.paint("h for help"));
836 println!("\n{}", state);
837 println!();
838 state.print_bytes();
839 }
840
841 loop {
843 if state.prefs.show_prompt {
844 print!("*");
845 }
846 io::stdout().flush().unwrap();
847 let input = match ec::get_input_or_die() {
848 Ok(input) => input,
849 Err(errcode) => {
850 return errcode;
851 }
852 };
853
854 match Command::from_state_and_line(&mut state, &input) {
855 Ok(command) => {
856 match command.command {
858
859 'e' => {
861 println!("?");
862 continue;
863 },
864
865 'g' => {
867 match ec::move_to(&mut state, command.range.0) {
868 Ok(_) => {
869 state.print_bytes();
870 },
871 Err(error) => {
872 println!("? ({})", error);
873 }
874 }
875 },
876
877 'G' => {
879 match pluses(&mut state, command.range.0) {
880 Err(error) => {
881 println!("? ({})", error);
882 },
883 Ok(_) => {
884 continue;
885 }
886 }
887 },
888
889 'H' => {
891 match minuses(&mut state, command.range.0) {
892 Err(error) => {
893 println!("? ({})", error);
894 },
895 Ok(_) => {
896 continue;
897 }
898 }
899 },
900
901 'i' => {
903 match read_bytes_from_user() {
904 Ok(entered_bytes) => {
905 state.index = command.range.1;
906 let mut new = Vec::with_capacity(state.all_bytes.len() + entered_bytes.len());
910 for i in 0..state.index {
911 new.push(state.all_bytes[i]);
912 }
913 for i in 0..entered_bytes.len() {
915 new.push(entered_bytes[i]);
916 }
917 for i in state.index..state.all_bytes.len() {
918 new.push(state.all_bytes[i]);
919 }
920 state.all_bytes = new;
921 state.unsaved_changes = true;
922 state.print_bytes_sans_context(state.range());
923 },
924 Err(error) => {
925 println!("? ({})", error);
926 },
927 }
928 },
929
930 'h' => {
932 print_help(&state);
933 },
934
935 'j' => {
937 if state.empty() {
938 println!("? (Empty file)");
939 continue;
940 };
941
942 let width = usize::from(state.prefs.width);
943 let first_byte_to_show_index =
944 state.index.saturating_sub(width);
945 state.index = first_byte_to_show_index;
946 state.print_bytes();
947 }
948
949
950 'k' => {
953 if state.empty() {
954 println!("? (Empty file");
955 continue;
956 }
957 skip_bad_range!(command, state.all_bytes);
958 let mut right_half = state.all_bytes.split_off(command.range.0);
959 right_half = right_half.split_off(command.range.1 - command.range.0 + 1);
960 state.all_bytes.append(&mut right_half);
961 state.index = command.range.0;
962 state.unsaved_changes = true;
963 state.print_bytes();
964 },
965
966
967 'l' => {
969 load_new_file(&mut state);
970 }
971
972 'L' => {
974 load_state_from_file(&mut state);
975 },
976
977 'm' => {
979 state.prefs.show_chars = !state.prefs.show_chars;
980 if !pipe_mode {
981 println!("{}", state.prefs.show_chars);
982 }
983 },
984
985 'n' => {
987 state.prefs.show_byte_numbers = !state.prefs.show_byte_numbers;
988 if !pipe_mode {
989 println!("{}", state.prefs.show_byte_numbers);
990 }
991 },
992
993 'o' => {
995 state.prefs.color = !state.prefs.color;
996 if !pipe_mode {
997 if state.prefs.color {
998 println!("{} mode on", state.pretty_color_state());
999 }
1000 else {
1001 println!("No color mode");
1002 }
1003 }
1004 },
1005
1006 'U' => {
1008 state.prefs.underline_main_line =
1009 if state.prefs.underline_main_line {
1010 false
1011 }
1012 else {
1013 true
1014 };
1015 }
1016
1017
1018 'v' => {
1020 state.breaks.insert(state.index);
1021 },
1022
1023 'V' => {
1025 state.breaks.remove(&state.index);
1026 },
1027
1028 'x' => {
1030 state.prefs.radix = if state.prefs.radix == 16 {
1031 10
1032 }
1033 else {
1034 16
1035 }
1036 },
1037
1038 '\n' => {
1040 if state.empty() {
1041 println!("? (Empty file)");
1042 continue;
1043 };
1044
1045 state.move_index_then_print_bytes();
1046 }
1047
1048 '☃' => {
1050 if state.empty() {
1051 println!("? (Empty file)");
1052 continue;
1053 };
1054
1055 skip_bad_range!(command, state.all_bytes);
1056 state.index = command.range.0;
1057 state.print_bytes_and_move_index();
1058 },
1059
1060 'p' => {
1062 if state.empty() {
1063 println!("? (Empty file)");
1064 continue;
1065 };
1066
1067 skip_bad_range!(command, state.all_bytes);
1068 state.index = command.range.0;
1069 if let Some(new_index) =
1070 state.print_bytes_sans_context(
1071 (command.range.0, command.range.1)) {
1072 state.index = new_index;
1073 }
1074 else {
1075 println!("? (no bytes in range {:?})",
1076 command.range);
1077 }
1078 },
1079
1080 'P' => {
1082 ec::save_to_path_or_default(&(state.prefs),
1083 "Enter filename to save preferences",
1084 ec::preferences_file_path());
1085 },
1086
1087
1088 'r' => {
1090 load_prefs(&mut state);
1091 },
1092
1093 'Q' => {
1095 if state.empty() {
1096 println!("? (Empty file)");
1097 continue;
1098 };
1099
1100 state.print_bytes();
1101 },
1102
1103 'q' => {
1105 return 0;
1106 },
1107
1108 'R' => {
1110 state.readonly = !state.readonly;
1111 },
1112
1113 'S' => {
1115 ec::save_to_path_or_default(&state,
1116 "Enter filename to save state",
1117 ec::state_file_path());
1118 },
1119
1120 's' => {
1122 println!("{}", state);
1123 },
1124
1125 't' => {
1127 state.prefs.after_context = usize::from(command.range.0);
1128 },
1129
1130 'T' => {
1132 state.prefs.before_context = usize::from(command.range.0);
1133 },
1134
1135 'u' => {
1137 update_filename(&mut state);
1138 },
1139
1140 'w' => {
1142 write_out(&mut state);
1143 },
1144
1145 'W' => {
1147 if let Some(width) = NonZeroUsize::new(command.range.0) {
1148 state.prefs.width = width;
1149 }
1150 },
1151
1152 _ => {
1154 println!("? (Don't understand command '{}')", command.command);
1155 continue;
1156 },
1157 }
1158 },
1159 Err(error) => {
1160 println!("? ({})", error);
1161 continue;
1162 }
1163 }
1164 }
1165}