Add command for resetting diff hunks (#5736)

This commit is contained in:
Pascal Kuthe 2023-03-08 02:49:14 +01:00 committed by GitHub
parent 8c2e447b16
commit 48b6aa9a69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 140 additions and 37 deletions

View file

@ -76,3 +76,4 @@
| `:pipe` | Pipe each selection to the shell command. | | `:pipe` | Pipe each selection to the shell command. |
| `:pipe-to` | Pipe each selection to the shell command, ignoring output. | | `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
| `:run-shell-command`, `:sh` | Run a shell command | | `:run-shell-command`, `:sh` | Run a shell command |
| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |

View file

@ -3011,13 +3011,13 @@ fn goto_first_change_impl(cx: &mut Context, reverse: bool) {
let (view, doc) = current!(editor); let (view, doc) = current!(editor);
if let Some(handle) = doc.diff_handle() { if let Some(handle) = doc.diff_handle() {
let hunk = { let hunk = {
let hunks = handle.hunks(); let diff = handle.load();
let idx = if reverse { let idx = if reverse {
hunks.len().saturating_sub(1) diff.len().saturating_sub(1)
} else { } else {
0 0
}; };
hunks.nth_hunk(idx) diff.nth_hunk(idx)
}; };
if hunk != Hunk::NONE { if hunk != Hunk::NONE {
let range = hunk_range(hunk, doc.text().slice(..)); 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 selection = doc.selection(view.id).clone().transform(|range| {
let cursor_line = range.cursor_line(doc_text) as u32; 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 { let hunk_idx = match direction {
Direction::Forward => hunks Direction::Forward => diff
.next_hunk(cursor_line) .next_hunk(cursor_line)
.map(|idx| (idx + count).min(hunks.len() - 1)), .map(|idx| (idx + count).min(diff.len() - 1)),
Direction::Backward => hunks Direction::Backward => diff
.prev_hunk(cursor_line) .prev_hunk(cursor_line)
.map(|idx| idx.saturating_sub(count)), .map(|idx| idx.saturating_sub(count)),
}; };
let Some(hunk_idx) = hunk_idx else { let Some(hunk_idx) = hunk_idx else {
return range; return range;
}; };
let hunk = hunks.nth_hunk(hunk_idx); let hunk = diff.nth_hunk(hunk_idx);
let new_range = hunk_range(hunk, doc_text); let new_range = hunk_range(hunk, doc_text);
if editor.mode == Mode::Select { if editor.mode == Mode::Select {
let head = if new_range.head < range.anchor { 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 textobject_change = |range: Range| -> Range {
let diff_handle = doc.diff_handle().unwrap(); let diff_handle = doc.diff_handle().unwrap();
let hunks = diff_handle.hunks(); let diff = diff_handle.load();
let line = range.cursor_line(text); 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 hunk_idx
} else { } else {
return range; 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 start = text.line_to_char(hunk.start as usize);
let end = text.line_to_char(hunk.end as usize); let end = text.line_to_char(hunk.end as usize);

View file

@ -2033,6 +2033,64 @@ fn run_shell_command(
Ok(()) 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] = &[ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand { TypableCommand {
name: "quit", name: "quit",
@ -2569,6 +2627,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: run_shell_command, fun: run_shell_command,
completer: Some(completers::filename), 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>> = pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =

View file

@ -28,11 +28,18 @@ struct Event {
render_lock: Option<RenderLock>, render_lock: Option<RenderLock>,
} }
#[derive(Clone, Debug, Default)]
struct DiffInner {
diff_base: Rope,
doc: Rope,
hunks: Vec<Hunk>,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DiffHandle { pub struct DiffHandle {
channel: UnboundedSender<Event>, channel: UnboundedSender<Event>,
render_lock: Arc<RwLock<()>>, render_lock: Arc<RwLock<()>>,
hunks: Arc<Mutex<Vec<Hunk>>>, diff: Arc<Mutex<DiffInner>>,
inverted: bool, inverted: bool,
} }
@ -47,10 +54,10 @@ impl DiffHandle {
redraw_handle: RedrawHandle, redraw_handle: RedrawHandle,
) -> (DiffHandle, JoinHandle<()>) { ) -> (DiffHandle, JoinHandle<()>) {
let (sender, receiver) = unbounded_channel(); let (sender, receiver) = unbounded_channel();
let hunks: Arc<Mutex<Vec<Hunk>>> = Arc::default(); let diff: Arc<Mutex<DiffInner>> = Arc::default();
let worker = DiffWorker { let worker = DiffWorker {
channel: receiver, channel: receiver,
hunks: hunks.clone(), diff: diff.clone(),
new_hunks: Vec::default(), new_hunks: Vec::default(),
redraw_notify: redraw_handle.0, redraw_notify: redraw_handle.0,
diff_finished_notify: Arc::default(), diff_finished_notify: Arc::default(),
@ -58,7 +65,7 @@ impl DiffHandle {
let handle = tokio::spawn(worker.run(diff_base, doc)); let handle = tokio::spawn(worker.run(diff_base, doc));
let differ = DiffHandle { let differ = DiffHandle {
channel: sender, channel: sender,
hunks, diff,
inverted: false, inverted: false,
render_lock: redraw_handle.1, render_lock: redraw_handle.1,
}; };
@ -69,9 +76,9 @@ impl DiffHandle {
self.inverted = !self.inverted; self.inverted = !self.inverted;
} }
pub fn hunks(&self) -> FileHunks { pub fn load(&self) -> Diff {
FileHunks { Diff {
hunks: self.hunks.lock(), diff: self.diff.lock(),
inverted: self.inverted, inverted: self.inverted,
} }
} }
@ -168,12 +175,28 @@ impl Hunk {
/// A list of changes in a file sorted in ascending /// A list of changes in a file sorted in ascending
/// non-overlapping order /// non-overlapping order
#[derive(Debug)] #[derive(Debug)]
pub struct FileHunks<'a> { pub struct Diff<'a> {
hunks: MutexGuard<'a, Vec<Hunk>>, diff: MutexGuard<'a, DiffInner>,
inverted: bool, 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 { pub fn is_inverted(&self) -> bool {
self.inverted self.inverted
} }
@ -181,7 +204,7 @@ impl FileHunks<'_> {
/// Returns the `Hunk` for the `n`th change in this file. /// Returns the `Hunk` for the `n`th change in this file.
/// if there is no `n`th change `Hunk::NONE` is returned instead. /// if there is no `n`th change `Hunk::NONE` is returned instead.
pub fn nth_hunk(&self, n: u32) -> Hunk { 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) if self.inverted => hunk.invert(),
Some(hunk) => hunk.clone(), Some(hunk) => hunk.clone(),
None => Hunk::NONE, None => Hunk::NONE,
@ -189,7 +212,7 @@ impl FileHunks<'_> {
} }
pub fn len(&self) -> u32 { pub fn len(&self) -> u32 {
self.hunks.len() as u32 self.diff.hunks.len() as u32
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
@ -204,19 +227,20 @@ impl FileHunks<'_> {
}; };
let res = self let res = self
.diff
.hunks .hunks
.binary_search_by_key(&line, |hunk| hunk_range(hunk).start); .binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
match res { match res {
// Search found a hunk that starts exactly at this line, return the next hunk if it exists. // 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), Ok(pos) => Some(pos as u32 + 1),
// No hunk starts exactly at this line, so the search returns // No hunk starts exactly at this line, so the search returns
// the position where a hunk starting at this line should be inserted. // 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 // That position is exactly the position of the next hunk or the end
// of the list if no such hunk exists // 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), Err(pos) => Some(pos as u32),
} }
} }
@ -228,6 +252,7 @@ impl FileHunks<'_> {
|hunk: &Hunk| hunk.after.clone() |hunk: &Hunk| hunk.after.clone()
}; };
let res = self let res = self
.diff
.hunks .hunks
.binary_search_by_key(&line, |hunk| hunk_range(hunk).end); .binary_search_by_key(&line, |hunk| hunk_range(hunk).end);
@ -237,7 +262,7 @@ impl FileHunks<'_> {
// which represents a pure removal. // which represents a pure removal.
// Removals are technically empty but are still shown as single line hunks // 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 // 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 // No hunk ends exactly at this line, so the search returns
// the position where a hunk ending at this line should be inserted. // the position where a hunk ending at this line should be inserted.
@ -255,6 +280,7 @@ impl FileHunks<'_> {
}; };
let res = self let res = self
.diff
.hunks .hunks
.binary_search_by_key(&line, |hunk| hunk_range(hunk).start); .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 // The previous hunk contains this hunk if it exists and doesn't end before this line
Err(0) => None, Err(0) => None,
Err(pos) => { 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() { if hunk.end > line || include_removal && hunk.start == line && hunk.is_empty() {
Some(pos as u32 - 1) Some(pos as u32 - 1)
} else { } else {

View file

@ -43,6 +43,14 @@ impl InternedRopeLines {
res 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 /// 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>) { pub fn update_diff_base(&mut self, diff_base: Rope, doc: Option<Rope>) {
self.interned.clear(); self.interned.clear();

View file

@ -10,7 +10,7 @@ use tokio::sync::Notify;
use tokio::time::{timeout, timeout_at, Duration}; use tokio::time::{timeout, timeout_at, Duration};
use crate::diff::{ 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; use super::line_cache::InternedRopeLines;
@ -21,7 +21,7 @@ mod test;
pub(super) struct DiffWorker { pub(super) struct DiffWorker {
pub channel: UnboundedReceiver<Event>, pub channel: UnboundedReceiver<Event>,
pub hunks: Arc<Mutex<Vec<Hunk>>>, pub diff: Arc<Mutex<DiffInner>>,
pub new_hunks: Vec<Hunk>, pub new_hunks: Vec<Hunk>,
pub redraw_notify: Arc<Notify>, pub redraw_notify: Arc<Notify>,
pub diff_finished_notify: Arc<Notify>, pub diff_finished_notify: Arc<Notify>,
@ -46,7 +46,7 @@ impl DiffWorker {
if let Some(lines) = interner.interned_lines() { if let Some(lines) = interner.interned_lines() {
self.perform_diff(lines); self.perform_diff(lines);
} }
self.apply_hunks(); self.apply_hunks(interner.diff_base(), interner.doc());
while let Some(event) = self.channel.recv().await { while let Some(event) = self.channel.recv().await {
let (doc, diff_base) = self.accumulate_events(event).await; let (doc, diff_base) = self.accumulate_events(event).await;
@ -70,15 +70,18 @@ impl DiffWorker {
#[cfg(not(test))] #[cfg(not(test))]
tokio::task::block_in_place(process_accumulated_events); 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`. /// update the hunks (used by the gutter) by replacing it with `self.new_hunks`.
/// `self.new_hunks` is always empty after this function runs. /// `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` /// 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) { fn apply_hunks(&mut self, diff_base: Rope, doc: Rope) {
swap(&mut *self.hunks.lock(), &mut self.new_hunks); 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.diff_finished_notify.notify_waiters();
self.new_hunks.clear(); self.new_hunks.clear();
} }

View file

@ -12,12 +12,12 @@ impl DiffHandle {
) )
} }
async fn into_diff(self, handle: JoinHandle<()>) -> Vec<Hunk> { async fn into_diff(self, handle: JoinHandle<()>) -> Vec<Hunk> {
let hunks = self.hunks; let diff = self.diff;
// dropping the channel terminates the task // dropping the channel terminates the task
drop(self.channel); drop(self.channel);
handle.await.unwrap(); handle.await.unwrap();
let hunks = hunks.lock(); let diff = diff.lock();
Vec::clone(&*hunks) Vec::clone(&diff.hunks)
} }
} }

View file

@ -100,7 +100,7 @@ pub fn diff<'doc>(
let deleted = theme.get("diff.minus"); let deleted = theme.get("diff.minus");
let modified = theme.get("diff.delta"); let modified = theme.get("diff.delta");
if let Some(diff_handle) = doc.diff_handle() { 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_i = 0;
let mut hunk = hunks.nth_hunk(hunk_i); let mut hunk = hunks.nth_hunk(hunk_i);
Box::new( Box::new(