add_ed/
lib.rs

1//#![deny(missing_docs)]
2
3//! Add-Ed is a library implementing the parsing, IO and runtime for Ed in rust.
4//!
5//! Behaviour is initially based off of [GNU Ed] with modifications to improve
6//! on usability. See the [readme] and [release notes] for details.
7//!
8//! This library exports two traits, [`IO`](io::IO) and [`UI`](ui::UI), which
9//! define the exchangeable parts of the editor. If you enable the `local_io`
10//! feature there is a ready implementation of the IO, but you will need to
11//! bring your own UI if you wish to do any user interaction. If you don't wish
12//! to do any user interaction, [`ScriptedUI`](ui::ScriptedUI) should be quite
13//! easy to use.
14//!
15//! Minimal scripted usage example:
16//! ```
17//! use add_ed::{
18//!   ui::ScriptedUI,
19//!   io::LocalIO,
20//!   Ed,
21//!   EdError,
22//! };
23//!
24//! # fn main() -> Result<(), EdError> {
25//! // Construct all the components
26//! let mut ui = ScriptedUI{
27//!   input: vec![format!("e {}\n", "Cargo.toml")].into(),
28//!   print_ui: None,
29//! };
30//! let macro_store = std::collections::HashMap::new();
31//! let mut io = LocalIO::new();
32//! // Construct and run ed
33//! let mut ed = Ed::new(&mut io, &macro_store);
34//! ed.run(&mut ui)?;
35//! # Ok(()) }
36//! ```
37//!
38//!
39//! A full example of how to use this library is in src/bin/classic-ed.rs
40//!
41//! [GNU Ed]: https://www.gnu.org/software/ed/manual/ed_manual.html
42//! [readme]: https://github.com/sidju/add-ed/blob/main/README.md
43//! [release notes]: https://github.com/sidju/add-ed/blob/main/RELEASE_NOTES.md
44
45
46pub mod messages;
47
48#[macro_use]
49pub mod error;
50pub use error::{
51  Result,
52  EdError,
53};
54
55mod cmd;
56
57pub mod ui;
58use ui::{UI, UILock, ScriptedUI};
59pub mod io;
60use io::IO;
61
62mod history;
63pub use history::History;
64pub mod macros;
65use macros::{Macro, MacroGetter};
66
67pub use buffer::iters::*;
68mod buffer;
69pub use buffer::{
70  LineText,
71  Line,
72  Buffer,
73  PubLine,
74  Clipboard,
75};
76
77/// A ready parsed 's' invocation, including command and printing flags
78pub struct Substitution {
79  /// Regex pattern to match against
80  pub pattern: String,
81  /// Substitution template to replace it with
82  pub substitute: String,
83  /// Set true to apply to all occurences (instead of only the first)
84  pub global: bool,
85  /// Flag to print after execution
86  pub p: bool,
87  /// Flag to print with line numbers after execution
88  pub n: bool,
89  /// Flag to print with literal escapes after execution
90  pub l: bool,
91}
92
93/// The state variable used to track the editor's internal state.
94///
95/// It is designed to support mutation and analysis by library users, but be
96/// careful: modifying this state wrong will cause user facing errors.
97pub struct Ed <'a> {
98  /// Holds the past, present and sometimes future states of the editing buffer
99  ///
100  /// See [`History`] documentation for how to use.
101  pub history: History<Buffer>,
102  /// The current clipboard contents
103  ///
104  /// Uses a special [`Buffer`] analogue over [`PubLine`], since some of the
105  /// internal data in Line could cause unexpected behavior if pasted as is.
106  pub clipboard: Clipboard,
107  /// Tracks the currently selected lines in the buffer.
108  ///
109  /// Inclusive 1-indexed start and end bounds over selected lines. Selected
110  /// lines aren't required to exist, but it is recommended for user comfort.
111  /// Empty selection should only occur when the buffer is empty, and in that
112  /// case exactly (1,0). Invalid selections cause errors, not crashes (verified
113  /// by fuzzing).
114  pub selection: (usize, usize),
115  /// Currently used IO implementor
116  ///
117  /// It will be used to handle file interactions and command execution as
118  /// required during command execution
119  pub io: &'a mut dyn IO,
120  /// The path to the currently selected file.
121  pub file: String,
122
123  /// Shell command last given by the user
124  ///
125  /// Fully processed, meaning all substitutions of % to current file and 
126  /// similar should already have occured before saving here.
127  /// (Currently saved before successful run, so may be invalid).
128  pub prev_shell_command: String,
129  /// The previous `s` commands arguments, to support repeating last `s` command
130  /// when no arguments are given to `s`.
131  pub prev_s: Option<Substitution>,
132
133  /// Configuration of prefix before command input.
134  ///
135  /// Traditionally ':' so set to that by default.
136  pub cmd_prefix: Option<char>,
137  /// Set default to print numbered lines.
138  ///
139  /// If set `n` printing flag behaviour inverts and disables line numbers.
140  pub n: bool,
141  /// Set default to print literal lines. (A type of escaped print.)
142  ///
143  /// If set `l` printing flag behaviour inverts and disables literal printing.
144  pub l: bool,
145  /// Wether or not to print errors when they occur.
146  ///
147  /// If not true Ed prints ? on error, expecting use of `h` command to get
148  /// the error.
149  pub print_errors: bool,
150  /// The previous error that occured.
151  ///
152  /// Is printed by `h` command.
153  ///
154  /// UI errors occuring outside of Ed should also be written to this variable,
155  /// so `h` prints the latest error that occured in the whole application.
156  pub error: Option<EdError>,
157  /// EXPERIMENTAL: Reference to accessor for macros.
158  pub macro_getter: &'a dyn MacroGetter,
159  /// Set how many recursions should be allowed.
160  ///
161  /// One recursion is counted as one macro or 'g'/'v'/'G'/'V' invocation. Under
162  /// 2 is likely to interfere with basic use, 4 will require that macros don't
163  /// call into eachother, 16 is unlikely to abort needlessly.
164  pub recursion_limit: usize,
165}
166
167impl <'a, > Ed <'a> {
168  /// Construct a new instance of Ed
169  ///
170  /// Defaults are as follow:
171  /// - `file`: empty string
172  /// - `clipboard`: empty clipboard
173  /// - `error`: `None`
174  /// - `print_errors`: `true`
175  /// - `n`: `false`,
176  /// - `l`: `false`,
177  /// - `cmd_prefix`: `Some(':')`
178  /// - `recursion_limit`: `16`
179  pub fn new(
180    io: &'a mut dyn IO,
181    macro_getter: &'a dyn MacroGetter,
182  ) -> Self {
183    let selection = (1,0);
184    Self {
185      // Init internal state
186      selection,
187      history: History::new(),
188      prev_s: None,
189      prev_shell_command: String::new(),
190      // Sane defaults for externally visible variables
191      file: String::new(),
192      clipboard: Clipboard::new(),
193      error: None,
194      print_errors: true,
195      n: false,
196      l: false,
197      cmd_prefix: Some(':'),
198      recursion_limit: 16,
199      // And the given values
200      io,
201      macro_getter,
202    }
203  }
204
205  /// Run the given command
206  ///
207  /// Returns true if the command was to quit
208  pub fn run_command(
209    &mut self,
210    ui: &mut dyn UI,
211    command: &str,
212  ) -> Result<bool> {
213    self.private_run_command(ui, command, 0)
214  }
215  // Exists to handle nesting depth, for nested 'g' invocations, without
216  // exposing that argument to the public interface (since it will always be 0
217  // when called from the public API).
218  fn private_run_command(
219    &mut self,
220    ui: &mut dyn UI,
221    command: &str,
222    recursion_depth: usize,
223  ) -> Result<bool> {
224    // Just hand execution into the cmd module
225    match cmd::run(self, ui, command, recursion_depth) {
226      // If error, note it in state
227      Err(e) => {
228        self.error = Some(e.clone());
229        Err(e)
230      },
231      x => x,
232    }
233  }
234
235  /// Get a single command from the UI and run it
236  ///
237  /// Returns true if the command was to quit, false otherwise.
238  /// Returns error if any occurs
239  pub fn get_and_run_command(
240    &mut self,
241    ui: &mut dyn UI,
242  ) -> Result<bool> {
243    self.private_get_and_run_command(ui, 0)
244  }
245  // Exists to handle nesting depth, for nested 'g' or ':' invocations, without
246  // exposing that argument to the public interface (since it will always be 0
247  // when called from the public API).
248  fn private_get_and_run_command(
249    &mut self,
250    ui: &mut dyn UI,
251    recursion_depth: usize,
252  ) -> Result<bool> {
253    // Define a temporary closure to catch UI errors, needed since try blocks
254    // aren't stabilized
255    let mut clos = || {
256      let cmd = ui.get_command(self, self.cmd_prefix)?;
257      self.private_run_command(ui, &cmd, recursion_depth)
258    };
259    // Run it, save any error, and forward result
260    match clos() {
261      Err(e) => {
262        self.error = Some(e.clone());
263        Err(e)
264      },
265      x => x,
266    }
267  }
268
269  /// Run given macro until Ed receives a command to quit or errors
270  ///
271  /// Will immediately return error if the macro was given wrong nr of arguments
272  ///
273  /// Returns Ok(()) when quit by command (or end of macro input)
274  pub fn run_macro<
275    S: std::ops::Deref<Target = str>,
276  >(
277    &mut self,
278    ui: &mut dyn UI,
279    mac: &Macro,
280    arguments: &[S],
281  ) -> Result<()> {
282    self.private_run_macro(ui, mac, arguments, 0)
283  }
284  // Exists to handle nesting depth, for nested ':' invocations, without
285  // exposing that argument to the public interface (since it will always be 0
286  // when called from the public API).
287  fn private_run_macro<
288    S: std::ops::Deref<Target = str>,
289  >(
290    &mut self,
291    ui: &mut dyn UI,
292    mac: &Macro,
293    arguments: &[S],
294    recursion_depth: usize,
295  ) -> Result<()> {
296    // Apply the arguments via templating
297    let to_run = macros::apply_arguments(mac, arguments)?;
298    // Construct a dummy UI with the given input
299    let mut script_ui = ScriptedUI{
300      input: to_run.lines().map(|x| format!("{}\n",x)).collect(),
301      print_ui: Some(ui),
302    };
303    // Loop over it, handling errors, until quit received
304    loop {
305      if self.private_get_and_run_command(&mut script_ui, recursion_depth)? {
306        break;
307      }
308    }
309    Ok(())
310  }
311
312  /// Run until quit by command
313  ///
314  /// Prints ? or error message as errors occur (depending on `print_errors`).
315  /// Returns error only if error occurs when printing an error.
316  pub fn run(
317    &mut self,
318    ui: &mut dyn UI,
319  ) -> Result<()> {
320    // Loop getting and running command, handling errors, until quit received
321    loop {
322      match self.get_and_run_command(ui) {
323        Ok(true) => break,
324        Ok(false) => (),
325        Err(e) => {
326          if self.print_errors {
327            ui.print_message(&e.to_string())?;
328          }
329          else {
330            ui.print_message("?\n")?;
331          }
332        },
333      }
334    }
335    Ok(())
336  }
337}