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