1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! Add-Ed is a library implementing the parsing and runtime for Ed in rust.
//!
//! It exports two traits, Buffer and UI, which define the exchangeable parts of the editor.
//!
//! An implementation of the UI trait is needed to support the 'g' command and similar, DummyUI.
//! It is used for macro execution, by taking prepared input from a input list rather than prompting the user.
//!
//! Since the buffer is rather complex a standard Buffer implementation can be build in with the feature "vecbuffer".
//! It is recommended to compare the behaviour of any Buffer implementation to the VecBuffer until Buffer tests are set up.
//!
//! An example of how to use this library is in src/bin/classic.rs

pub mod error_consts;
mod cmd;

pub mod ui;
pub mod buffer;

use ui::UI;
use buffer::Buffer;

/// The state variable used by the editor to track its internal state
pub struct Ed <'a, B: Buffer> {
  // Track the currently selected lines in the buffer
  // This is usually separate from viewed lines in the UI
  selection: Option<(usize, usize)>,
  // A mutable reference to a Buffer implementor
  // The buffer implementor will handle most of the operations and store the data
  buffer: &'a mut B,

  // The path to the currently selected file
  path: String,

  // The previous search_replace's arguments, to support repeating the last
  s_args: Option<(String, String, bool)>,

  // Wether or not to print errors when they occur (if not, print ? instead of error)
  print_errors: bool,
  // The previous error that occured, since we may not have printed it
  error: Option<&'static str>,
}

impl <'a, B: Buffer> Ed <'a, B> {
  /// Construct a new instance of Ed
  ///
  /// * An empty file string is recommended if no filepath is opened
  /// * Note that you can initialise the buffer with contents before this
  pub fn new(
    buffer: &'a mut B,
    path: String,
  ) -> Result<Self, &'static str> {
    let len = path.len();
    if len != 0 {
      buffer.read_from(&path, None, false)?;
    }
    let tmp = Self {
      // Sane defaults for initial settings
      print_errors: true,
      error: None,
      s_args: None,
      // Trying to set a reasonable default tends to cause trouble
      selection: None,
      // And the given values
      buffer: buffer,
      path: path,
    };
    Ok(tmp)
  }

  /// Run the given command
  ///
  /// Returns true if the command was to quit
  pub fn run_command(
    &mut self,
    ui: &mut dyn UI,
    command: &str,
  ) -> Result<bool, &'static str> {
    // Just hand execution into the cmd module
    match cmd::run(self, ui, command) {
      // If error, note it in state
      Err(e) => {
        self.error = Some(e);
        Err(e)
      },
      x => x,
    }
  }

  /// Run given instance of Ed until it receives a command to quit or errors
  ///
  /// The returned error type could be improved, suggestions welcome.
  pub fn run_macro(
    &mut self,
    ui: &mut dyn UI,
  ) -> Result<(), &'static str> {
    // Loop until quit or error
    loop {
      let cmd = match ui.get_command( self.buffer ) {
        Err(e) => { self.error = Some(e); return Err(e) },
        Ok(x) => x,
      };
      if self.run_command(ui, &cmd)? {
        break;
      }
    }
    Ok(())
  }
  pub fn run(
    &mut self,
    ui: &mut dyn UI,
  ) -> Result<(), &'static str> {
    loop {
      match self.run_macro(ui) {
        Ok(()) => break,
        Err(_) => {
          if self.print_errors {
            ui.print(self.error.unwrap())?;
          }
          else {
            ui.print("?\n")?;
          }
        },
      }
    }
    Ok(())
  }
}