aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPascal Kuthe2023-03-08 01:49:14 +0000
committerGitHub2023-03-08 01:49:14 +0000
commit48b6aa9a699df0680a6d31e9611ebd1ca9909de4 (patch)
tree11459e49578000678f494857cfbe1f60bfe2ba20
parent8c2e447b16e4d11db411b18f2fbe3ac2bc031d89 (diff)
Add command for resetting diff hunks (#5736)
-rw-r--r--book/src/generated/typable-cmd.md1
-rw-r--r--helix-term/src/commands.rs22
-rw-r--r--helix-term/src/commands/typed.rs65
-rw-r--r--helix-vcs/src/diff.rs58
-rw-r--r--helix-vcs/src/diff/line_cache.rs8
-rw-r--r--helix-vcs/src/diff/worker.rs15
-rw-r--r--helix-vcs/src/diff/worker/test.rs6
-rw-r--r--helix-view/src/gutter.rs2
8 files changed, 140 insertions, 37 deletions
diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md
index badadc43..8b367aad 100644
--- a/book/src/generated/typable-cmd.md
+++ b/book/src/generated/typable-cmd.md
@@ -76,3 +76,4 @@
| `:pipe` | Pipe each selection to the shell command. |
| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
| `:run-shell-command`, `:sh` | Run a shell command |
+| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index c76e9f2b..ebdfdfde 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -3011,13 +3011,13 @@ fn goto_first_change_impl(cx: &mut Context, reverse: bool) {
let (view, doc) = current!(editor);
if let Some(handle) = doc.diff_handle() {
let hunk = {
- let hunks = handle.hunks();
+ let diff = handle.load();
let idx = if reverse {
- hunks.len().saturating_sub(1)
+ diff.len().saturating_sub(1)
} else {
0
};
- hunks.nth_hunk(idx)
+ diff.nth_hunk(idx)
};
if hunk != Hunk::NONE {
let range = hunk_range(hunk, doc.text().slice(..));
@@ -3049,19 +3049,19 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
let selection = doc.selection(view.id).clone().transform(|range| {
let cursor_line = range.cursor_line(doc_text) as u32;
- let hunks = diff_handle.hunks();
+ let diff = diff_handle.load();
let hunk_idx = match direction {
- Direction::Forward => hunks
+ Direction::Forward => diff
.next_hunk(cursor_line)
- .map(|idx| (idx + count).min(hunks.len() - 1)),
- Direction::Backward => hunks
+ .map(|idx| (idx + count).min(diff.len() - 1)),
+ Direction::Backward => diff
.prev_hunk(cursor_line)
.map(|idx| idx.saturating_sub(count)),
};
let Some(hunk_idx) = hunk_idx else {
return range;
};
- let hunk = hunks.nth_hunk(hunk_idx);
+ let hunk = diff.nth_hunk(hunk_idx);
let new_range = hunk_range(hunk, doc_text);
if editor.mode == Mode::Select {
let head = if new_range.head < range.anchor {
@@ -4721,14 +4721,14 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
let textobject_change = |range: Range| -> Range {
let diff_handle = doc.diff_handle().unwrap();
- let hunks = diff_handle.hunks();
+ let diff = diff_handle.load();
let line = range.cursor_line(text);
- let hunk_idx = if let Some(hunk_idx) = hunks.hunk_at(line as u32, false) {
+ let hunk_idx = if let Some(hunk_idx) = diff.hunk_at(line as u32, false) {
hunk_idx
} else {
return range;
};
- let hunk = hunks.nth_hunk(hunk_idx).after;
+ let hunk = diff.nth_hunk(hunk_idx).after;
let start = text.line_to_char(hunk.start as usize);
let end = text.line_to_char(hunk.end as usize);
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index 0ddca6df..77c14321 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -2033,6 +2033,64 @@ fn run_shell_command(
Ok(())
}
+fn reset_diff_change(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
+ if event != PromptEvent::Validate {
+ return Ok(());
+ }
+ ensure!(args.is_empty(), ":reset-diff-change takes no arguments");
+
+ let editor = &mut cx.editor;
+ let scrolloff = editor.config().scrolloff;
+
+ let (view, doc) = current!(editor);
+ // TODO refactor to use let..else once MSRV is raised to 1.65
+ let handle = match doc.diff_handle() {
+ Some(handle) => handle,
+ None => bail!("Diff is not available in the current buffer"),
+ };
+
+ let diff = handle.load();
+ let doc_text = doc.text().slice(..);
+ let line = doc.selection(view.id).primary().cursor_line(doc_text);
+
+ // TODO refactor to use let..else once MSRV is raised to 1.65
+ let hunk_idx = match diff.hunk_at(line as u32, true) {
+ Some(hunk_idx) => hunk_idx,
+ None => bail!("There is no change at the cursor"),
+ };
+ let hunk = diff.nth_hunk(hunk_idx);
+ let diff_base = diff.diff_base();
+ let before_start = diff_base.line_to_char(hunk.before.start as usize);
+ let before_end = diff_base.line_to_char(hunk.before.end as usize);
+ let text: Tendril = diff
+ .diff_base()
+ .slice(before_start..before_end)
+ .chunks()
+ .collect();
+ let anchor = doc_text.line_to_char(hunk.after.start as usize);
+ let transaction = Transaction::change(
+ doc.text(),
+ [(
+ anchor,
+ doc_text.line_to_char(hunk.after.end as usize),
+ (!text.is_empty()).then_some(text),
+ )]
+ .into_iter(),
+ );
+ drop(diff); // make borrow check happy
+ doc.apply(&transaction, view.id);
+ // select inserted text
+ let text_len = before_end - before_start;
+ doc.set_selection(view.id, Selection::single(anchor, anchor + text_len));
+ doc.append_changes_to_history(view);
+ view.ensure_cursor_in_view(doc, scrolloff);
+ Ok(())
+}
+
pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "quit",
@@ -2569,6 +2627,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: run_shell_command,
completer: Some(completers::filename),
},
+ TypableCommand {
+ name: "reset-diff-change",
+ aliases: &["diffget", "diffg"],
+ doc: "Reset the diff change at the cursor position.",
+ fun: reset_diff_change,
+ completer: None,
+ },
];
pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
diff --git a/helix-vcs/src/diff.rs b/helix-vcs/src/diff.rs
index 9c6a362f..ca33dda4 100644
--- a/helix-vcs/src/diff.rs
+++ b/helix-vcs/src/diff.rs
@@ -28,11 +28,18 @@ struct Event {
render_lock: Option<RenderLock>,
}
+#[derive(Clone, Debug, Default)]
+struct DiffInner {
+ diff_base: Rope,
+ doc: Rope,
+ hunks: Vec<Hunk>,
+}
+
#[derive(Clone, Debug)]
pub struct DiffHandle {
channel: UnboundedSender<Event>,
render_lock: Arc<RwLock<()>>,
- hunks: Arc<Mutex<Vec<Hunk>>>,
+ diff: Arc<Mutex<DiffInner>>,
inverted: bool,
}
@@ -47,10 +54,10 @@ impl DiffHandle {
redraw_handle: RedrawHandle,
) -> (DiffHandle, JoinHandle<()>) {
let (sender, receiver) = unbounded_channel();
- let hunks: Arc<Mutex<Vec<Hunk>>> = Arc::default();
+ let diff: Arc<Mutex<DiffInner>> = Arc::default();
let worker = DiffWorker {
channel: receiver,
- hunks: hunks.clone(),
+ diff: diff.clone(),
new_hunks: Vec::default(),
redraw_notify: redraw_handle.0,
diff_finished_notify: Arc::default(),
@@ -58,7 +65,7 @@ impl DiffHandle {
let handle = tokio::spawn(worker.run(diff_base, doc));
let differ = DiffHandle {
channel: sender,
- hunks,
+ diff,
inverted: false,
render_lock: redraw_handle.1,
};
@@ -69,9 +76,9 @@ impl DiffHandle {
self.inverted = !self.inverted;
}
- pub fn hunks(&self) -> FileHunks {
- FileHunks {
- hunks: self.hunks.lock(),
+ pub fn load(&self) -> Diff {
+ Diff {
+ diff: self.diff.lock(),
inverted: self.inverted,
}
}
@@ -168,12 +175,28 @@ impl Hunk {
/// A list of changes in a file sorted in ascending
/// non-overlapping order
#[derive(Debug)]
-pub struct FileHunks<'a> {
- hunks: MutexGuard<'a, Vec<Hunk>>,
+pub struct Diff<'a> {
+ diff: MutexGuard<'a, DiffInner>,
inverted: bool,
}
-impl FileHunks<'_> {
+impl Diff<'_> {
+ pub fn diff_base(&self) -> &Rope {
+ if self.inverted {
+ &self.diff.doc
+ } else {
+ &self.diff.diff_base
+ }
+ }
+
+ pub fn doc(&self) -> &Rope {
+ if self.inverted {
+ &self.diff.diff_base
+ } else {
+ &self.diff.doc
+ }
+ }
+
pub fn is_inverted(&self) -> bool {
self.inverted
}
@@ -181,7 +204,7 @@ impl FileHunks<'_> {
/// Returns the `Hunk` for the `n`th change in this file.
/// if there is no `n`th change `Hunk::NONE` is returned instead.
pub fn nth_hunk(&self, n: u32) -> Hunk {
- match self.hunks.get(n as usize) {
+ match self.diff.hunks.get(n as usize) {
Some(hunk) if self.inverted => hunk.invert(),
Some(hunk) => hunk.clone(),
None => Hunk::NONE,
@@ -189,7 +212,7 @@ impl FileHunks<'_> {
}
pub fn len(&self) -> u32 {
- self.hunks.len() as u32
+ self.diff.hunks.len() as u32
}
pub fn is_empty(&self) -> bool {
@@ -204,19 +227,20 @@ impl FileHunks<'_> {
};
let res = self
+ .diff
.hunks
.binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
match res {
// Search found a hunk that starts exactly at this line, return the next hunk if it exists.
- Ok(pos) if pos + 1 == self.hunks.len() => None,
+ Ok(pos) if pos + 1 == self.diff.hunks.len() => None,
Ok(pos) => Some(pos as u32 + 1),
// No hunk starts exactly at this line, so the search returns
// the position where a hunk starting at this line should be inserted.
// That position is exactly the position of the next hunk or the end
// of the list if no such hunk exists
- Err(pos) if pos == self.hunks.len() => None,
+ Err(pos) if pos == self.diff.hunks.len() => None,
Err(pos) => Some(pos as u32),
}
}
@@ -228,6 +252,7 @@ impl FileHunks<'_> {
|hunk: &Hunk| hunk.after.clone()
};
let res = self
+ .diff
.hunks
.binary_search_by_key(&line, |hunk| hunk_range(hunk).end);
@@ -237,7 +262,7 @@ impl FileHunks<'_> {
// which represents a pure removal.
// Removals are technically empty but are still shown as single line hunks
// and as such we must jump to the previous hunk (if it exists) if we are already inside the removal
- Ok(pos) if !hunk_range(&self.hunks[pos]).is_empty() => Some(pos as u32),
+ Ok(pos) if !hunk_range(&self.diff.hunks[pos]).is_empty() => Some(pos as u32),
// No hunk ends exactly at this line, so the search returns
// the position where a hunk ending at this line should be inserted.
@@ -255,6 +280,7 @@ impl FileHunks<'_> {
};
let res = self
+ .diff
.hunks
.binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
@@ -267,7 +293,7 @@ impl FileHunks<'_> {
// The previous hunk contains this hunk if it exists and doesn't end before this line
Err(0) => None,
Err(pos) => {
- let hunk = hunk_range(&self.hunks[pos - 1]);
+ let hunk = hunk_range(&self.diff.hunks[pos - 1]);
if hunk.end > line || include_removal && hunk.start == line && hunk.is_empty() {
Some(pos as u32 - 1)
} else {
diff --git a/helix-vcs/src/diff/line_cache.rs b/helix-vcs/src/diff/line_cache.rs
index c3ee5daa..8e48250f 100644
--- a/helix-vcs/src/diff/line_cache.rs
+++ b/helix-vcs/src/diff/line_cache.rs
@@ -43,6 +43,14 @@ impl InternedRopeLines {
res
}
+ pub fn doc(&self) -> Rope {
+ self.doc.clone()
+ }
+
+ pub fn diff_base(&self) -> Rope {
+ self.diff_base.clone()
+ }
+
/// Updates the `diff_base` and optionally the document if `doc` is not None
pub fn update_diff_base(&mut self, diff_base: Rope, doc: Option<Rope>) {
self.interned.clear();
diff --git a/helix-vcs/src/diff/worker.rs b/helix-vcs/src/diff/worker.rs
index f4bb4dbf..5406446f 100644
--- a/helix-vcs/src/diff/worker.rs
+++ b/helix-vcs/src/diff/worker.rs
@@ -10,7 +10,7 @@ use tokio::sync::Notify;
use tokio::time::{timeout, timeout_at, Duration};
use crate::diff::{
- Event, RenderLock, ALGORITHM, DIFF_DEBOUNCE_TIME_ASYNC, DIFF_DEBOUNCE_TIME_SYNC,
+ DiffInner, Event, RenderLock, ALGORITHM, DIFF_DEBOUNCE_TIME_ASYNC, DIFF_DEBOUNCE_TIME_SYNC,
};
use super::line_cache::InternedRopeLines;
@@ -21,7 +21,7 @@ mod test;
pub(super) struct DiffWorker {
pub channel: UnboundedReceiver<Event>,
- pub hunks: Arc<Mutex<Vec<Hunk>>>,
+ pub diff: Arc<Mutex<DiffInner>>,
pub new_hunks: Vec<Hunk>,
pub redraw_notify: Arc<Notify>,
pub diff_finished_notify: Arc<Notify>,
@@ -46,7 +46,7 @@ impl DiffWorker {
if let Some(lines) = interner.interned_lines() {
self.perform_diff(lines);
}
- self.apply_hunks();
+ self.apply_hunks(interner.diff_base(), interner.doc());
while let Some(event) = self.channel.recv().await {
let (doc, diff_base) = self.accumulate_events(event).await;
@@ -70,15 +70,18 @@ impl DiffWorker {
#[cfg(not(test))]
tokio::task::block_in_place(process_accumulated_events);
- self.apply_hunks();
+ self.apply_hunks(interner.diff_base(), interner.doc());
}
}
/// update the hunks (used by the gutter) by replacing it with `self.new_hunks`.
/// `self.new_hunks` is always empty after this function runs.
/// To improve performance this function tries to reuse the allocation of the old diff previously stored in `self.line_diffs`
- fn apply_hunks(&mut self) {
- swap(&mut *self.hunks.lock(), &mut self.new_hunks);
+ fn apply_hunks(&mut self, diff_base: Rope, doc: Rope) {
+ let mut diff = self.diff.lock();
+ diff.diff_base = diff_base;
+ diff.doc = doc;
+ swap(&mut diff.hunks, &mut self.new_hunks);
self.diff_finished_notify.notify_waiters();
self.new_hunks.clear();
}
diff --git a/helix-vcs/src/diff/worker/test.rs b/helix-vcs/src/diff/worker/test.rs
index 14442426..6a68d987 100644
--- a/helix-vcs/src/diff/worker/test.rs
+++ b/helix-vcs/src/diff/worker/test.rs
@@ -12,12 +12,12 @@ impl DiffHandle {
)
}
async fn into_diff(self, handle: JoinHandle<()>) -> Vec<Hunk> {
- let hunks = self.hunks;
+ let diff = self.diff;
// dropping the channel terminates the task
drop(self.channel);
handle.await.unwrap();
- let hunks = hunks.lock();
- Vec::clone(&*hunks)
+ let diff = diff.lock();
+ Vec::clone(&diff.hunks)
}
}
diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs
index cb9e4333..36e8e16a 100644
--- a/helix-view/src/gutter.rs
+++ b/helix-view/src/gutter.rs
@@ -100,7 +100,7 @@ pub fn diff<'doc>(
let deleted = theme.get("diff.minus");
let modified = theme.get("diff.delta");
if let Some(diff_handle) = doc.diff_handle() {
- let hunks = diff_handle.hunks();
+ let hunks = diff_handle.load();
let mut hunk_i = 0;
let mut hunk = hunks.nth_hunk(hunk_i);
Box::new(