edhex/
lib.rs

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
10// TODO This is deprecated and should be
11// replaced with
12//     ec = {package = "edhex_core", version = "0.1.0}
13// in Cargo.toml.  But that's only going to
14// work for after Rust 1.26.0  Far enough in the future, use the Cargo.toml way.
15extern 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        // TODO Make these constants outside of this function so they don't get
116        // created over and over
117        // TODO Allow general whitespace, not just literal spaces
118        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            /* Notice looking after current byte */
264            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            // println!("is_width");
398            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            // println!("is_range");
424            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                // Why on Earth doesn't this work?
429                // return Err(begin.unwrap());
430                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                // Why on Earth doesn't this work?
437                // return end;
438                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            // println!("is_specified_index");
468            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                // Why on Earth doesn't this work?
473                // return specific_index;
474                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            // println!("is_specified_index");
505            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
562/// Returns new index number
563fn 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
580/// Returns new index number
581fn 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    /* Nothing's been written to that file yet, so */
627    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    /* Early return if write unsuccessful */
747    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        /* filename is a string */
763        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
777/// If `filename` is "", open an empty buffer
778pub 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    /* Use a state file if one is present */
789    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    /* Use a config file if one is present */
830    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    // TODO Below here should be a function called main_loop()
842    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                // println!("{:?}", command);
857                match command.command {
858
859                    /* Error */
860                    'e' => {
861                        println!("?");
862                        continue;
863                    },
864
865                    /* Go to */
866                    '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                    /* +'s */
878                    '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                    /* -'s */
890                    '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                    /* insert */
902                    'i' => {
903                        match read_bytes_from_user() {
904                            Ok(entered_bytes) => {
905                                state.index = command.range.1;
906                                // TODO Find the cheapest way to do this (maybe
907                                // make state.all_bytes a better container)
908                                // TODO Do this with split_off
909                                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                                // TODO Could use Vec::splice here
914                                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                    /* Help */
931                    'h' => {
932                        print_help(&state);
933                    },
934
935                    /* User wants to go up a line */
936                    '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'ill byte(s) (Can't use 'd' because that's a hex
951                    * character! */
952                    '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                    /* Load new file */
968                    'l' => {
969                        load_new_file(&mut state);
970                    }
971
972                    /* Load state from a file */
973                    'L' => {
974                        load_state_from_file(&mut state);
975                    },
976
977                    /* Toggle showing char representations of bytes */
978                    'm' => {
979                        state.prefs.show_chars = !state.prefs.show_chars;
980                        if !pipe_mode {
981                            println!("{}", state.prefs.show_chars);
982                        }
983                    },
984
985                    /* Toggle showing byte number */
986                    '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                    /* Toggle color */
994                    '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                    /* Toggle underlining non-context line */
1007                    '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                    /* Insert a break in the display at current byte */
1019                    'v' => {
1020                        state.breaks.insert(state.index);
1021                    },
1022
1023                    /* Remove a break in the display at current byte */
1024                    'V' => {
1025                        state.breaks.remove(&state.index);
1026                    },
1027
1028                    /* Toggle hex/dec */
1029                    'x' => {
1030                        state.prefs.radix = if state.prefs.radix == 16 {
1031                            10
1032                        }
1033                        else {
1034                            16
1035                        }
1036                    },
1037
1038                    /* User pressed enter */
1039                    '\n' => {
1040                        if state.empty() {
1041                            println!("? (Empty file)");
1042                            continue;
1043                        };
1044
1045                        state.move_index_then_print_bytes();
1046                    }
1047
1048                    /* Print byte(s) at one place, width long */
1049                    '☃' => {
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                    /* Print byte(s) with range */
1061                    '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                    /* Save preferences to a file */
1081                    '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                    /* Load preferences from a file */
1089                    'r' => {
1090                        load_prefs(&mut state);
1091                    },
1092
1093                    /* Print byte(s) at *current* place, width long */
1094                    'Q' => {
1095                        if state.empty() {
1096                            println!("? (Empty file)");
1097                            continue;
1098                        };
1099
1100                        state.print_bytes();
1101                    },
1102
1103                    /* Quit */
1104                    'q' => {
1105                        return 0;
1106                    },
1107
1108                    /* Toggle readonly mode */
1109                    'R' => {
1110                        state.readonly = !state.readonly;
1111                    },
1112
1113                    /* Write state to a file */
1114                    'S' => {
1115                        ec::save_to_path_or_default(&state,
1116                                "Enter filename to save state",
1117                                ec::state_file_path());
1118                    },
1119
1120                    /* Print state */
1121                    's' => {
1122                        println!("{}", state);
1123                    },
1124
1125                    /* Change after_context */
1126                    't' => {
1127                        state.prefs.after_context = usize::from(command.range.0);
1128                    },
1129
1130                    /* Change before_context */
1131                    'T' => {
1132                        state.prefs.before_context = usize::from(command.range.0);
1133                    },
1134
1135                    /* (u)pdate iflename */
1136                    'u' => {
1137                        update_filename(&mut state);
1138                    },
1139
1140                    /* Write out */
1141                    'w' => {
1142                        write_out(&mut state);
1143                    },
1144
1145                    /* Change width */
1146                    'W' => {
1147                        if let Some(width) = NonZeroUsize::new(command.range.0) {
1148                            state.prefs.width = width;
1149                        }
1150                    },
1151
1152                    /* Catchall error */
1153                    _ => {
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}