Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

:reload #374

Merged
merged 27 commits into from
Jul 2, 2021
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
35 changes: 35 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions helix-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,10 @@ regex = "1"
serde = { version = "1.0", features = ["derive"] }
toml = "0.5"

similar = "1.3"

etcetera = "0.3"
rust-embed = { version = "5.9.0", optional = true }

[dev-dependencies]
quickcheck = { version = "1", default-features = false }
70 changes: 70 additions & 0 deletions helix-core/src/diff.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use ropey::Rope;

use crate::{Change, Transaction};

/// Compares `old` and `new` to generate a [`Transaction`] describing
/// the steps required to get from `old` to `new`.
pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction {
// `similar` only works on contiguous data, so a `Rope` has
// to be temporarily converted into a `String`.
let old_converted = old.to_string();
let new_converted = new.to_string();

// A timeout is set so after 1 seconds, the algorithm will start
// approximating. This is especially important for big `Rope`s or
// `Rope`s that are extremely dissimilar to each other.
//
// Note: Ignore the clippy warning, as the trait bounds of
// `Transaction::change()` require an iterator implementing
// `ExactIterator`.
let mut config = similar::TextDiff::configure();
config.timeout(std::time::Duration::from_secs(1));

let diff = config.diff_chars(&old_converted, &new_converted);

// The current position of the change needs to be tracked to
// construct the `Change`s.
let mut pos = 0;
let changes: Vec<Change> = diff
.ops()
.iter()
.map(|op| op.as_tag_tuple())
.filter_map(|(tag, old_range, new_range)| {
// `old_pos..pos` is equivalent to `start..end` for where
// the change should be applied.
let old_pos = pos;
pos += old_range.end - old_range.start;

match tag {
// Semantically, inserts and replacements are the same thing.
similar::DiffTag::Insert | similar::DiffTag::Replace => {
// This is the text from the `new` rope that should be
// inserted into `old`.
let text: &str = {
let start = new.char_to_byte(new_range.start);
let end = new.char_to_byte(new_range.end);
&new_converted[start..end]
};
Some((old_pos, pos, Some(text.into())))
}
similar::DiffTag::Delete => Some((old_pos, pos, None)),
similar::DiffTag::Equal => None,
}
})
.collect();
Transaction::change(old, changes.into_iter())
}

#[cfg(test)]
mod tests {
use super::*;

quickcheck::quickcheck! {
fn test_compare_ropes(a: String, b: String) -> bool {
let mut old = Rope::from(a);
let new = Rope::from(b);
compare_ropes(&old, &new).apply(&mut old);
old.to_string() == new.to_string()
}
}
}
1 change: 1 addition & 0 deletions helix-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod auto_pairs;
pub mod chars;
pub mod comment;
pub mod diagnostic;
pub mod diff;
pub mod graphemes;
pub mod history;
pub mod indent;
Expand Down
32 changes: 32 additions & 0 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,24 @@ mod cmd {
}
}

/// Sets the [`Document`]'s encoding..
fn set_encoding(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) {
let (_, doc) = current!(cx.editor);
if let Some(label) = args.first() {
doc.set_encoding(label)
.unwrap_or_else(|e| cx.editor.set_error(e.to_string()));
} else {
let encoding = doc.encoding().name().to_string();
cx.editor.set_status(encoding)
}
}

/// Reload the [`Document`] from its source file.
fn reload(cx: &mut compositor::Context, _args: &[&str], _: PromptEvent) {
let (view, doc) = current!(cx.editor);
doc.reload(view.id).unwrap();
}
cessen marked this conversation as resolved.
Show resolved Hide resolved

pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "quit",
Expand Down Expand Up @@ -1704,6 +1722,20 @@ mod cmd {
fun: show_current_directory,
completer: None,
},
TypableCommand {
name: "encoding",
alias: None,
doc: "Set encoding based on `https://encoding.spec.whatwg.org`",
fun: set_encoding,
completer: None,
},
TypableCommand {
name: "reload",
alias: None,
doc: "Discard changes and reload from the source file.",
fun: reload,
completer: None,
}
];

pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
Expand Down
61 changes: 53 additions & 8 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,19 @@ pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
Ok(())
}

/// Inserts the final line ending into `rope` if it's missing. [Why?](https://stackoverflow.com/questions/729692/why-should-text-files-end-with-a-newline)
pub fn with_line_ending(rope: &mut Rope) -> LineEnding {
// search for line endings
let line_ending = auto_detect_line_ending(rope).unwrap_or(DEFAULT_LINE_ENDING);

// add missing newline at the end of file
if rope.len_bytes() == 0 || !char_is_line_ending(rope.char(rope.len_chars() - 1)) {
rope.insert(rope.len_chars(), line_ending.as_str());
}

line_ending
}

cessen marked this conversation as resolved.
Show resolved Hide resolved
/// Like std::mem::replace() except it allows the replacement value to be mapped from the
/// original value.
fn take_with<T, F>(mut_ref: &mut T, closure: F)
Expand Down Expand Up @@ -449,14 +462,7 @@ impl Document {

let mut file = std::fs::File::open(&path).context(format!("unable to open {:?}", path))?;
let (mut rope, encoding) = from_reader(&mut file, encoding)?;

// search for line endings
let line_ending = auto_detect_line_ending(&rope).unwrap_or(DEFAULT_LINE_ENDING);

// add missing newline at the end of file
if rope.len_bytes() == 0 || !char_is_line_ending(rope.char(rope.len_chars() - 1)) {
rope.insert(rope.len_chars(), line_ending.as_str());
}
let line_ending = with_line_ending(&mut rope);

let mut doc = Self::from(rope, Some(encoding));

Expand Down Expand Up @@ -586,6 +592,45 @@ impl Document {
}
}

/// Reload the document from its path.
pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> {
let encoding = &self.encoding;
let path = self.path().filter(|path| path.exists());

// If there is no path or the path no longer exists.
if path.is_none() {
return Err(anyhow!("can't find file to reload from"));
}

let mut file = std::fs::File::open(path.unwrap())?;
let (mut rope, ..) = from_reader(&mut file, Some(encoding))?;
let line_ending = with_line_ending(&mut rope);

let transaction = helix_core::diff::compare_ropes(self.text(), &rope);
self.apply(&transaction, view_id);
self.append_changes_to_history(view_id);
kirawi marked this conversation as resolved.
Show resolved Hide resolved

// Detect indentation style and set line ending.
self.detect_indent_style();
self.line_ending = line_ending;

Ok(())
}

/// Sets the [`Document`]'s encoding with the encoding correspondent to `label`.
pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
match encoding_rs::Encoding::for_label(label.as_bytes()) {
Some(encoding) => self.encoding = encoding,
None => return Err(anyhow::anyhow!("unknown encoding")),
}
Ok(())
}

/// Returns the [`Document`]'s current encoding.
pub fn encoding(&self) -> &'static encoding_rs::Encoding {
self.encoding
}

fn detect_indent_style(&mut self) {
// Build a histogram of the indentation *increases* between
// subsequent lines, ignoring lines that are all whitespace.
Expand Down