Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "add-ed"
description = "Embeddable pure rust editor based on ED"
version = "0.13.0"
version = "0.14.0"
repository = "https://github.com/sidju/add-ed"
readme = "README.md"
categories = ["text-editors"]
Expand Down Expand Up @@ -40,3 +40,6 @@ serde = { version = "1", features = ["derive"], optional = true }
[[bin]]
name = "classic-ed"
required-features = ["bin_deps"]

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
17 changes: 17 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
# 0.14.0

Mainly fixes longstanding bugs, but due to these changing some index behaviours
I'll call it a minor version.

- Improve index handling, especially for commands that use the end of the
selection by default.
- Catch empty filepath before we ask the filesystem, and better error message.
- Fix that line input from an inline macro (/command/input/) would create
invalid lines (without a terminating newline).
- Fix that `/` reached an unhandled codepath, by rewriting index parsing.
Maybe this time I got it right?
- Fix a possible error where a transforming command printing its last line
without a newline would cause an invalid line and an error.
- Improve fuzzing to panic on internal errors, to better avoid those occuring in
the wild.

# 0.13.0

- Change macro arg specification to an enum, for better: flexibility,
Expand Down
2 changes: 1 addition & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "add-ed-fuzz"
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
edition = "2018"
edition = "2021"

[package.metadata]
cargo-fuzz = true
Expand Down
10 changes: 7 additions & 3 deletions fuzz/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ use add_ed::io::fake_io::FakeIO;

// You can do anything in 4 input lines, so that should be enough
// (and it should help with the out-of-memory problems while fuzzing)
fuzz_target!(|data: [String; 4]| {
fuzz_target!(|data: &str| {
let mut ui = add_ed::ui::ScriptedUI {
input: data.into(),
input: data.split('\n').map(|x| format!("{}\n", x)).collect(),
print_ui: None,
};
let mut io = FakeIO{fake_fs: HashMap::new(), fake_shell: HashMap::new()};
let macro_store = HashMap::new();
let mut ed = add_ed::Ed::new(&mut io, &macro_store);
loop {
if let Ok(true) = ed.get_and_run_command(&mut ui) { break; }
match ed.get_and_run_command(&mut ui) {
Ok(true) => break, // Needed to not infinitely ask for input that doesn't exist
Err(add_ed::EdError::Internal(e)) => panic!("This is a real bug! {e:?}"),
_ => ()
}
}
});
6 changes: 3 additions & 3 deletions src/buffer/iters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct LinesIter<'a> {
// Wrapped by struct, so we can hide the internal state
enum LinesIterInner<'a> {
Real(Inner<'a>),
#[cfg(any(feature = "testing", fuzzing, test))]
#[cfg(any(fuzzing, test))]
Test(Box<dyn Iterator<Item = &'a str>>),
}

Expand All @@ -34,7 +34,7 @@ impl<'a> Iterator for LinesIter<'a> {
fn next(&mut self) -> Option<Self::Item> {
match &mut self.inner {
LinesIterInner::Real(x) => x.next(),
#[cfg(any(feature = "testing", fuzzing, test))]
#[cfg(any(fuzzing, test))]
LinesIterInner::Test(x) => x.next(),
}
}
Expand All @@ -46,7 +46,7 @@ impl<'a> From<Inner<'a>> for LinesIter<'a> {
}
}

#[cfg(any(feature = "testing", fuzzing, test))]
#[cfg(any(fuzzing, test))]
impl<'a, I: Iterator<Item=&'a str> + 'static> From<Box<I>> for LinesIter<'a> {
fn from(i: Box<I>) -> Self {
Self{ inner: LinesIterInner::Test(i) }
Expand Down
34 changes: 19 additions & 15 deletions src/cmd/editing_commands/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,21 +77,31 @@ pub fn input(
command: char,
flags: &str,
) -> Result<()> {
let sel = interpret_selection(&state, selection, state.selection)?;
let mut flags = parse_flags(flags, "pnl")?;
pflags.p = flags.remove(&'p').unwrap();
pflags.n = flags.remove(&'n').unwrap();
pflags.l = flags.remove(&'l').unwrap();

let buffer = state.history.current();
match command {
'a' => buffer.verify_index(sel.1)?,
let index = match command {
'a' | 'A' => {
let i = interpret_index_from_selection(&state, selection, state.selection, true)?;
if command == 'a' { buffer.verify_index(i)? } else { buffer.verify_line(i)? }
i
},
// Note that saturating_sub really is needed, since inserting at index 0
// should be valid and equivalent to inserting at index 1.
'i' => buffer.verify_index(sel.0.saturating_sub(1))?,
'A' => buffer.verify_line(sel.1)?,
'I' => buffer.verify_line(sel.0)?,
_ => { panic!("Unreachable code reached"); }
}
'i' | 'I' => {
let mut i = interpret_index_from_selection(&state, selection, state.selection, false)?;
if command == 'i' {
i = i.saturating_sub(1);
buffer.verify_index(i)?;
}
else { buffer.verify_line(i)? }
i
},
_ => ed_unreachable!()?,
};
// Now that we have checked that the command is valid, get input
// This is done so we don't drop text input, which would be annoying
let input = ui.get_input(
Expand All @@ -104,12 +114,6 @@ pub fn input(
// TODO: replace this post-execution selection prediction with returns from
// the inner functions.
state.selection = if !input.is_empty() {
let index = match command {
'a' | 'A' => sel.1,
'i' => sel.0.saturating_sub(1),
'I' => sel.0,
_ => unreachable!(),
};
let start = index + 1; // since buffer.insert puts input after index
let end = start + input.len() - 1; // Subtract for inclusive select
// In the case of 'a', 'i' that is all
Expand All @@ -128,7 +132,7 @@ pub fn input(
inner_input(state, full_command, input, index)?;
(start, end)
},
_ => { panic!("Unreachable code reached"); }
_ => ed_unreachable!()?,
}
}
// If no input is given, keep old selection
Expand Down
16 changes: 5 additions & 11 deletions src/cmd/editing_commands/paste.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,16 @@ pub fn paste(
command: char,
tail: &str,
) -> Result<()> {
let sel = interpret_selection(&state, selection, state.selection)?;
let mut flags = parse_flags(tail, "pnl")?;
pflags.p = flags.remove(&'p').unwrap();
pflags.n = flags.remove(&'n').unwrap();
pflags.l = flags.remove(&'l').unwrap();
// Append or prepend based on command
let index =
if command == 'X' { sel.0.saturating_sub(1) }
else { sel.1 }
;
let mut index = interpret_index_from_selection(&state, selection, state.selection, command == 'x')?;
if command == 'X' { index = index.saturating_sub(1); }
let length = inner_paste(state, full_command, index)?;
state.selection =
if length != 0 {
(index + 1, index + length)
}
else { sel }
;
if length != 0 {
state.selection = (index + 1, index + length);
}
Ok(())
}
23 changes: 15 additions & 8 deletions src/cmd/editing_commands/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,21 @@ pub fn transfer(
let selection = interpret_selection(&state, selection, state.selection)?;
// Parse the target index, then the flags if any
let (ind_end, ind) = parse_index(tail)?;
let index = interpret_index(
&state,
ind.unwrap_or_else(||{
if command == 'M' || command == 'T' { Ind::Literal(1) }
else { Ind::BufferLen }
}),
state.selection.1,
)?;
let index = if command == 'm' || command == 't' {
interpret_index(
&state,
ind.unwrap_or_else(|| Ind::BufferLen),
state.selection.1,
)?
}
// We still keep the handling for 'T' and 'M', we may re-enable it later
else {
interpret_index(
&state,
ind.unwrap_or_else(|| Ind::Literal(1)),
state.selection.0,
)?
};
let mut flags = parse_flags(&tail[ind_end..], "pnl")?;
pflags.p = flags.remove(&'p').unwrap();
pflags.n = flags.remove(&'n').unwrap();
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/io_commands/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub fn read_from_file(
) -> Result<()> {
let index =
if command == 'r' {
let i = interpret_selection(&state, selection, state.selection)?.1;
let i = interpret_index_from_selection(&state, selection, state.selection, true)?;
state.history.current().verify_index(i)?;
Ok(Some(i))
}
Expand Down
3 changes: 2 additions & 1 deletion src/cmd/io_commands/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,12 @@ pub fn run_command(
// replace it with the output
Some(s) => {
let data = state.history.current().get_lines(s)?;
let transformed = state.io.run_transform_command(
let mut transformed = state.io.run_transform_command(
&mut ui.lock_ui(),
substituted,
data,
)?;
if !transformed.ends_with('\n') { transformed.push('\n'); }
let lines: Vec<&str> = transformed.split_inclusive('\n').collect();
let nr_lines = lines.len();
replace_selection(state, full_command, s, lines)?;
Expand Down
Loading