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, ¯o_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}