on master help sign in
Add command for resetting diff hunks (#5736)
commit48b6aa9a699df0680a6d31e9611ebd1ca9909de4 authorPascal Kuthe <pascal.kuthe@semimod.de> dateWed 8 Mar 2023, 02:49 (4 years ago) parent8c2e447
8 files changed+140−37
book/src/generated/typable-cmd.md+1 helix-term/src/commands.rs+11−11 helix-term/src/commands/typed.rs+65 helix-vcs/src/diff.rs+42−16 helix-vcs/src/diff/line_cache.rs+8 helix-vcs/src/diff/worker.rs+9−6 helix-vcs/src/diff/worker/test.rs+3−3 helix-view/src/gutter.rs+1−1
book/src/generated/typable-cmd.md+1
@@ -76,3 +76,4 @@
7676| `:pipe` | Pipe each selection to the shell command. |
7777| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
7878| `:run-shell-command`, `:sh` | Run a shell command |
79+| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
helix-term/src/commands.rs+11−11
@@ -3011,13 +3011,13 @@ fn goto_first_change_impl(cx: &mut Context, reverse: bool) {
30113011 let (view, doc) = current!(editor);
30123012 if let Some(handle) = doc.diff_handle() {
30133013 let hunk = {
3014− let hunks = handle.hunks();
3014+ let diff = handle.load();
30153015 let idx = if reverse {
3016− hunks.len().saturating_sub(1)
3016+ diff.len().saturating_sub(1)
30173017 } else {
30183018 0
30193019 };
3020− hunks.nth_hunk(idx)
3020+ diff.nth_hunk(idx)
30213021 };
30223022 if hunk != Hunk::NONE {
30233023 let range = hunk_range(hunk, doc.text().slice(..));
@@ -3049,19 +3049,19 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
30493049 let selection = doc.selection(view.id).clone().transform(|range| {
30503050 let cursor_line = range.cursor_line(doc_text) as u32;
30513051
3052− let hunks = diff_handle.hunks();
3052+ let diff = diff_handle.load();
30533053 let hunk_idx = match direction {
3054− Direction::Forward => hunks
3054+ Direction::Forward => diff
30553055 .next_hunk(cursor_line)
3056− .map(|idx| (idx + count).min(hunks.len() - 1)),
3057− Direction::Backward => hunks
3056+ .map(|idx| (idx + count).min(diff.len() - 1)),
3057+ Direction::Backward => diff
30583058 .prev_hunk(cursor_line)
30593059 .map(|idx| idx.saturating_sub(count)),
30603060 };
30613061 let Some(hunk_idx) = hunk_idx else {
30623062 return range;
30633063 };
3064− let hunk = hunks.nth_hunk(hunk_idx);
3064+ let hunk = diff.nth_hunk(hunk_idx);
30653065 let new_range = hunk_range(hunk, doc_text);
30663066 if editor.mode == Mode::Select {
30673067 let head = if new_range.head < range.anchor {
@@ -4721,14 +4721,14 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
47214721
47224722 let textobject_change = |range: Range| -> Range {
47234723 let diff_handle = doc.diff_handle().unwrap();
4724− let hunks = diff_handle.hunks();
4724+ let diff = diff_handle.load();
47254725 let line = range.cursor_line(text);
4726− let hunk_idx = if let Some(hunk_idx) = hunks.hunk_at(line as u32, false) {
4726+ let hunk_idx = if let Some(hunk_idx) = diff.hunk_at(line as u32, false) {
47274727 hunk_idx
47284728 } else {
47294729 return range;
47304730 };
4731− let hunk = hunks.nth_hunk(hunk_idx).after;
4731+ let hunk = diff.nth_hunk(hunk_idx).after;
47324732
47334733 let start = text.line_to_char(hunk.start as usize);
47344734 let end = text.line_to_char(hunk.end as usize);
helix-term/src/commands/typed.rs+65
@@ -2033,6 +2033,64 @@ fn run_shell_command(
20332033 Ok(())
20342034}
20352035
2036+fn reset_diff_change(
2037+ cx: &mut compositor::Context,
2038+ args: &[Cow<str>],
2039+ event: PromptEvent,
2040+) -> anyhow::Result<()> {
2041+ if event != PromptEvent::Validate {
2042+ return Ok(());
2043+ }
2044+ ensure!(args.is_empty(), ":reset-diff-change takes no arguments");
2045+
2046+ let editor = &mut cx.editor;
2047+ let scrolloff = editor.config().scrolloff;
2048+
2049+ let (view, doc) = current!(editor);
2050+ // TODO refactor to use let..else once MSRV is raised to 1.65
2051+ let handle = match doc.diff_handle() {
2052+ Some(handle) => handle,
2053+ None => bail!("Diff is not available in the current buffer"),
2054+ };
2055+
2056+ let diff = handle.load();
2057+ let doc_text = doc.text().slice(..);
2058+ let line = doc.selection(view.id).primary().cursor_line(doc_text);
2059+
2060+ // TODO refactor to use let..else once MSRV is raised to 1.65
2061+ let hunk_idx = match diff.hunk_at(line as u32, true) {
2062+ Some(hunk_idx) => hunk_idx,
2063+ None => bail!("There is no change at the cursor"),
2064+ };
2065+ let hunk = diff.nth_hunk(hunk_idx);
2066+ let diff_base = diff.diff_base();
2067+ let before_start = diff_base.line_to_char(hunk.before.start as usize);
2068+ let before_end = diff_base.line_to_char(hunk.before.end as usize);
2069+ let text: Tendril = diff
2070+ .diff_base()
2071+ .slice(before_start..before_end)
2072+ .chunks()
2073+ .collect();
2074+ let anchor = doc_text.line_to_char(hunk.after.start as usize);
2075+ let transaction = Transaction::change(
2076+ doc.text(),
2077+ [(
2078+ anchor,
2079+ doc_text.line_to_char(hunk.after.end as usize),
2080+ (!text.is_empty()).then_some(text),
2081+ )]
2082+ .into_iter(),
2083+ );
2084+ drop(diff); // make borrow check happy
2085+ doc.apply(&transaction, view.id);
2086+ // select inserted text
2087+ let text_len = before_end - before_start;
2088+ doc.set_selection(view.id, Selection::single(anchor, anchor + text_len));
2089+ doc.append_changes_to_history(view);
2090+ view.ensure_cursor_in_view(doc, scrolloff);
2091+ Ok(())
2092+}
2093+
20362094pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
20372095 TypableCommand {
20382096 name: "quit",
@@ -2569,6 +2627,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
25692627 fun: run_shell_command,
25702628 completer: Some(completers::filename),
25712629 },
2630+ TypableCommand {
2631+ name: "reset-diff-change",
2632+ aliases: &["diffget", "diffg"],
2633+ doc: "Reset the diff change at the cursor position.",
2634+ fun: reset_diff_change,
2635+ completer: None,
2636+ },
25722637 ];
25732638
25742639pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
helix-vcs/src/diff.rs+42−16
@@ -28,11 +28,18 @@ struct Event {
2828 render_lock: Option<RenderLock>,
2929}
3030
31+#[derive(Clone, Debug, Default)]
32+struct DiffInner {
33+ diff_base: Rope,
34+ doc: Rope,
35+ hunks: Vec<Hunk>,
36+}
37+
3138#[derive(Clone, Debug)]
3239pub struct DiffHandle {
3340 channel: UnboundedSender<Event>,
3441 render_lock: Arc<RwLock<()>>,
35− hunks: Arc<Mutex<Vec<Hunk>>>,
42+ diff: Arc<Mutex<DiffInner>>,
3643 inverted: bool,
3744}
3845
@@ -47,10 +54,10 @@ impl DiffHandle {
4754 redraw_handle: RedrawHandle,
4855 ) -> (DiffHandle, JoinHandle<()>) {
4956 let (sender, receiver) = unbounded_channel();
50− let hunks: Arc<Mutex<Vec<Hunk>>> = Arc::default();
57+ let diff: Arc<Mutex<DiffInner>> = Arc::default();
5158 let worker = DiffWorker {
5259 channel: receiver,
53− hunks: hunks.clone(),
60+ diff: diff.clone(),
5461 new_hunks: Vec::default(),
5562 redraw_notify: redraw_handle.0,
5663 diff_finished_notify: Arc::default(),
@@ -58,7 +65,7 @@ impl DiffHandle {
5865 let handle = tokio::spawn(worker.run(diff_base, doc));
5966 let differ = DiffHandle {
6067 channel: sender,
61− hunks,
68+ diff,
6269 inverted: false,
6370 render_lock: redraw_handle.1,
6471 };
@@ -69,9 +76,9 @@ impl DiffHandle {
6976 self.inverted = !self.inverted;
7077 }
7178
72− pub fn hunks(&self) -> FileHunks {
73− FileHunks {
74− hunks: self.hunks.lock(),
79+ pub fn load(&self) -> Diff {
80+ Diff {
81+ diff: self.diff.lock(),
7582 inverted: self.inverted,
7683 }
7784 }
@@ -168,12 +175,28 @@ impl Hunk {
168175/// A list of changes in a file sorted in ascending
169176/// non-overlapping order
170177#[derive(Debug)]
171−pub struct FileHunks<'a> {
172− hunks: MutexGuard<'a, Vec<Hunk>>,
178+pub struct Diff<'a> {
179+ diff: MutexGuard<'a, DiffInner>,
173180 inverted: bool,
174181}
175182
176−impl FileHunks<'_> {
183+impl Diff<'_> {
184+ pub fn diff_base(&self) -> &Rope {
185+ if self.inverted {
186+ &self.diff.doc
187+ } else {
188+ &self.diff.diff_base
189+ }
190+ }
191+
192+ pub fn doc(&self) -> &Rope {
193+ if self.inverted {
194+ &self.diff.diff_base
195+ } else {
196+ &self.diff.doc
197+ }
198+ }
199+
177200 pub fn is_inverted(&self) -> bool {
178201 self.inverted
179202 }
@@ -181,7 +204,7 @@ impl FileHunks<'_> {
181204 /// Returns the `Hunk` for the `n`th change in this file.
182205 /// if there is no `n`th change `Hunk::NONE` is returned instead.
183206 pub fn nth_hunk(&self, n: u32) -> Hunk {
184− match self.hunks.get(n as usize) {
207+ match self.diff.hunks.get(n as usize) {
185208 Some(hunk) if self.inverted => hunk.invert(),
186209 Some(hunk) => hunk.clone(),
187210 None => Hunk::NONE,
@@ -189,7 +212,7 @@ impl FileHunks<'_> {
189212 }
190213
191214 pub fn len(&self) -> u32 {
192− self.hunks.len() as u32
215+ self.diff.hunks.len() as u32
193216 }
194217
195218 pub fn is_empty(&self) -> bool {
@@ -204,19 +227,20 @@ impl FileHunks<'_> {
204227 };
205228
206229 let res = self
230+ .diff
207231 .hunks
208232 .binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
209233
210234 match res {
211235 // Search found a hunk that starts exactly at this line, return the next hunk if it exists.
212− Ok(pos) if pos + 1 == self.hunks.len() => None,
236+ Ok(pos) if pos + 1 == self.diff.hunks.len() => None,
213237 Ok(pos) => Some(pos as u32 + 1),
214238
215239 // No hunk starts exactly at this line, so the search returns
216240 // the position where a hunk starting at this line should be inserted.
217241 // That position is exactly the position of the next hunk or the end
218242 // of the list if no such hunk exists
219− Err(pos) if pos == self.hunks.len() => None,
243+ Err(pos) if pos == self.diff.hunks.len() => None,
220244 Err(pos) => Some(pos as u32),
221245 }
222246 }
@@ -228,6 +252,7 @@ impl FileHunks<'_> {
228252 |hunk: &Hunk| hunk.after.clone()
229253 };
230254 let res = self
255+ .diff
231256 .hunks
232257 .binary_search_by_key(&line, |hunk| hunk_range(hunk).end);
233258
@@ -237,7 +262,7 @@ impl FileHunks<'_> {
237262 // which represents a pure removal.
238263 // Removals are technically empty but are still shown as single line hunks
239264 // and as such we must jump to the previous hunk (if it exists) if we are already inside the removal
240− Ok(pos) if !hunk_range(&self.hunks[pos]).is_empty() => Some(pos as u32),
265+ Ok(pos) if !hunk_range(&self.diff.hunks[pos]).is_empty() => Some(pos as u32),
241266
242267 // No hunk ends exactly at this line, so the search returns
243268 // the position where a hunk ending at this line should be inserted.
@@ -255,6 +280,7 @@ impl FileHunks<'_> {
255280 };
256281
257282 let res = self
283+ .diff
258284 .hunks
259285 .binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
260286
@@ -267,7 +293,7 @@ impl FileHunks<'_> {
267293 // The previous hunk contains this hunk if it exists and doesn't end before this line
268294 Err(0) => None,
269295 Err(pos) => {
270− let hunk = hunk_range(&self.hunks[pos - 1]);
296+ let hunk = hunk_range(&self.diff.hunks[pos - 1]);
271297 if hunk.end > line || include_removal && hunk.start == line && hunk.is_empty() {
272298 Some(pos as u32 - 1)
273299 } else {
helix-vcs/src/diff/line_cache.rs+8
@@ -43,6 +43,14 @@ impl InternedRopeLines {
4343 res
4444 }
4545
46+ pub fn doc(&self) -> Rope {
47+ self.doc.clone()
48+ }
49+
50+ pub fn diff_base(&self) -> Rope {
51+ self.diff_base.clone()
52+ }
53+
4654 /// Updates the `diff_base` and optionally the document if `doc` is not None
4755 pub fn update_diff_base(&mut self, diff_base: Rope, doc: Option<Rope>) {
4856 self.interned.clear();
helix-vcs/src/diff/worker.rs+9−6
@@ -10,7 +10,7 @@ use tokio::sync::Notify;
1010use tokio::time::{timeout, timeout_at, Duration};
1111
1212use crate::diff::{
13− Event, RenderLock, ALGORITHM, DIFF_DEBOUNCE_TIME_ASYNC, DIFF_DEBOUNCE_TIME_SYNC,
13+ DiffInner, Event, RenderLock, ALGORITHM, DIFF_DEBOUNCE_TIME_ASYNC, DIFF_DEBOUNCE_TIME_SYNC,
1414};
1515
1616use super::line_cache::InternedRopeLines;
@@ -21,7 +21,7 @@ mod test;
2121
2222pub(super) struct DiffWorker {
2323 pub channel: UnboundedReceiver<Event>,
24− pub hunks: Arc<Mutex<Vec<Hunk>>>,
24+ pub diff: Arc<Mutex<DiffInner>>,
2525 pub new_hunks: Vec<Hunk>,
2626 pub redraw_notify: Arc<Notify>,
2727 pub diff_finished_notify: Arc<Notify>,
@@ -46,7 +46,7 @@ impl DiffWorker {
4646 if let Some(lines) = interner.interned_lines() {
4747 self.perform_diff(lines);
4848 }
49− self.apply_hunks();
49+ self.apply_hunks(interner.diff_base(), interner.doc());
5050 while let Some(event) = self.channel.recv().await {
5151 let (doc, diff_base) = self.accumulate_events(event).await;
5252
@@ -70,15 +70,18 @@ impl DiffWorker {
7070 #[cfg(not(test))]
7171 tokio::task::block_in_place(process_accumulated_events);
7272
73− self.apply_hunks();
73+ self.apply_hunks(interner.diff_base(), interner.doc());
7474 }
7575 }
7676
7777 /// update the hunks (used by the gutter) by replacing it with `self.new_hunks`.
7878 /// `self.new_hunks` is always empty after this function runs.
7979 /// To improve performance this function tries to reuse the allocation of the old diff previously stored in `self.line_diffs`
80− fn apply_hunks(&mut self) {
81− swap(&mut *self.hunks.lock(), &mut self.new_hunks);
80+ fn apply_hunks(&mut self, diff_base: Rope, doc: Rope) {
81+ let mut diff = self.diff.lock();
82+ diff.diff_base = diff_base;
83+ diff.doc = doc;
84+ swap(&mut diff.hunks, &mut self.new_hunks);
8285 self.diff_finished_notify.notify_waiters();
8386 self.new_hunks.clear();
8487 }
helix-vcs/src/diff/worker/test.rs+3−3
@@ -12,12 +12,12 @@ impl DiffHandle {
1212 )
1313 }
1414 async fn into_diff(self, handle: JoinHandle<()>) -> Vec<Hunk> {
15− let hunks = self.hunks;
15+ let diff = self.diff;
1616 // dropping the channel terminates the task
1717 drop(self.channel);
1818 handle.await.unwrap();
19− let hunks = hunks.lock();
20− Vec::clone(&*hunks)
19+ let diff = diff.lock();
20+ Vec::clone(&diff.hunks)
2121 }
2222}
2323
helix-view/src/gutter.rs+1−1
@@ -100,7 +100,7 @@ pub fn diff<'doc>(
100100 let deleted = theme.get("diff.minus");
101101 let modified = theme.get("diff.delta");
102102 if let Some(diff_handle) = doc.diff_handle() {
103− let hunks = diff_handle.hunks();
103+ let hunks = diff_handle.load();
104104 let mut hunk_i = 0;
105105 let mut hunk = hunks.nth_hunk(hunk_i);
106106 Box::new(