on master help sign in
Add command for resetting diff hunks (#5736)
48b6aa9Pascal Kuthe
book/src/generated/typable-cmd.md+1
@@ -76,3 +76,4 @@
76| `:pipe` | Pipe each selection to the shell command. |
76| `:pipe` | Pipe each selection to the shell command. |
77| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
77| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
78| `:run-shell-command`, `:sh` | Run a shell command |
78| `: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) {
3011 let (view, doc) = current!(editor);
3011 let (view, doc) = current!(editor);
3012 if let Some(handle) = doc.diff_handle() {
3012 if let Some(handle) = doc.diff_handle() {
3013 let hunk = {
3013 let hunk = {
3014− let hunks = handle.hunks();
3014+ let diff = handle.load();
3015 let idx = if reverse {
3015 let idx = if reverse {
3016− hunks.len().saturating_sub(1)
3016+ diff.len().saturating_sub(1)
3017 } else {
3017 } else {
3018 0
3018 0
3019 };
3019 };
3020− hunks.nth_hunk(idx)
3020+ diff.nth_hunk(idx)
3021 };
3021 };
3022 if hunk != Hunk::NONE {
3022 if hunk != Hunk::NONE {
3023 let range = hunk_range(hunk, doc.text().slice(..));
3023 let range = hunk_range(hunk, doc.text().slice(..));
@@ -3049,19 +3049,19 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
3049 let selection = doc.selection(view.id).clone().transform(|range| {
3049 let selection = doc.selection(view.id).clone().transform(|range| {
3050 let cursor_line = range.cursor_line(doc_text) as u32;
3050 let cursor_line = range.cursor_line(doc_text) as u32;
3051
3051
3052− let hunks = diff_handle.hunks();
3052+ let diff = diff_handle.load();
3053 let hunk_idx = match direction {
3053 let hunk_idx = match direction {
3054− Direction::Forward => hunks
3054+ Direction::Forward => diff
3055 .next_hunk(cursor_line)
3055 .next_hunk(cursor_line)
3056− .map(|idx| (idx + count).min(hunks.len() - 1)),
3056+ .map(|idx| (idx + count).min(diff.len() - 1)),
3057− Direction::Backward => hunks
3057+ Direction::Backward => diff
3058 .prev_hunk(cursor_line)
3058 .prev_hunk(cursor_line)
3059 .map(|idx| idx.saturating_sub(count)),
3059 .map(|idx| idx.saturating_sub(count)),
3060 };
3060 };
3061 let Some(hunk_idx) = hunk_idx else {
3061 let Some(hunk_idx) = hunk_idx else {
3062 return range;
3062 return range;
3063 };
3063 };
3064− let hunk = hunks.nth_hunk(hunk_idx);
3064+ let hunk = diff.nth_hunk(hunk_idx);
3065 let new_range = hunk_range(hunk, doc_text);
3065 let new_range = hunk_range(hunk, doc_text);
3066 if editor.mode == Mode::Select {
3066 if editor.mode == Mode::Select {
3067 let head = if new_range.head < range.anchor {
3067 let head = if new_range.head < range.anchor {
@@ -4721,14 +4721,14 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
4721
4721
4722 let textobject_change = |range: Range| -> Range {
4722 let textobject_change = |range: Range| -> Range {
4723 let diff_handle = doc.diff_handle().unwrap();
4723 let diff_handle = doc.diff_handle().unwrap();
4724− let hunks = diff_handle.hunks();
4724+ let diff = diff_handle.load();
4725 let line = range.cursor_line(text);
4725 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) {
4727 hunk_idx
4727 hunk_idx
4728 } else {
4728 } else {
4729 return range;
4729 return range;
4730 };
4730 };
4731− let hunk = hunks.nth_hunk(hunk_idx).after;
4731+ let hunk = diff.nth_hunk(hunk_idx).after;
4732
4732
4733 let start = text.line_to_char(hunk.start as usize);
4733 let start = text.line_to_char(hunk.start as usize);
4734 let end = text.line_to_char(hunk.end as usize);
4734 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(
2033 Ok(())
2033 Ok(())
2034}
2034}
2035
2035
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+
2036pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2094pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2037 TypableCommand {
2095 TypableCommand {
2038 name: "quit",
2096 name: "quit",
@@ -2569,6 +2627,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2569 fun: run_shell_command,
2627 fun: run_shell_command,
2570 completer: Some(completers::filename),
2628 completer: Some(completers::filename),
2571 },
2629 },
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+ },
2572 ];
2637 ];
2573
2638
2574pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
2639pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
helix-vcs/src/diff.rs+42−16
@@ -28,11 +28,18 @@ struct Event {
28 render_lock: Option<RenderLock>,
28 render_lock: Option<RenderLock>,
29}
29}
30
30
31+#[derive(Clone, Debug, Default)]
32+struct DiffInner {
33+ diff_base: Rope,
34+ doc: Rope,
35+ hunks: Vec<Hunk>,
36+}
37+
31#[derive(Clone, Debug)]
38#[derive(Clone, Debug)]
32pub struct DiffHandle {
39pub struct DiffHandle {
33 channel: UnboundedSender<Event>,
40 channel: UnboundedSender<Event>,
34 render_lock: Arc<RwLock<()>>,
41 render_lock: Arc<RwLock<()>>,
35− hunks: Arc<Mutex<Vec<Hunk>>>,
42+ diff: Arc<Mutex<DiffInner>>,
36 inverted: bool,
43 inverted: bool,
37}
44}
38
45
@@ -47,10 +54,10 @@ impl DiffHandle {
47 redraw_handle: RedrawHandle,
54 redraw_handle: RedrawHandle,
48 ) -> (DiffHandle, JoinHandle<()>) {
55 ) -> (DiffHandle, JoinHandle<()>) {
49 let (sender, receiver) = unbounded_channel();
56 let (sender, receiver) = unbounded_channel();
50− let hunks: Arc<Mutex<Vec<Hunk>>> = Arc::default();
57+ let diff: Arc<Mutex<DiffInner>> = Arc::default();
51 let worker = DiffWorker {
58 let worker = DiffWorker {
52 channel: receiver,
59 channel: receiver,
53− hunks: hunks.clone(),
60+ diff: diff.clone(),
54 new_hunks: Vec::default(),
61 new_hunks: Vec::default(),
55 redraw_notify: redraw_handle.0,
62 redraw_notify: redraw_handle.0,
56 diff_finished_notify: Arc::default(),
63 diff_finished_notify: Arc::default(),
@@ -58,7 +65,7 @@ impl DiffHandle {
58 let handle = tokio::spawn(worker.run(diff_base, doc));
65 let handle = tokio::spawn(worker.run(diff_base, doc));
59 let differ = DiffHandle {
66 let differ = DiffHandle {
60 channel: sender,
67 channel: sender,
61− hunks,
68+ diff,
62 inverted: false,
69 inverted: false,
63 render_lock: redraw_handle.1,
70 render_lock: redraw_handle.1,
64 };
71 };
@@ -69,9 +76,9 @@ impl DiffHandle {
69 self.inverted = !self.inverted;
76 self.inverted = !self.inverted;
70 }
77 }
71
78
72− pub fn hunks(&self) -> FileHunks {
79+ pub fn load(&self) -> Diff {
73− FileHunks {
80+ Diff {
74− hunks: self.hunks.lock(),
81+ diff: self.diff.lock(),
75 inverted: self.inverted,
82 inverted: self.inverted,
76 }
83 }
77 }
84 }
@@ -168,12 +175,28 @@ impl Hunk {
168/// A list of changes in a file sorted in ascending
175/// A list of changes in a file sorted in ascending
169/// non-overlapping order
176/// non-overlapping order
170#[derive(Debug)]
177#[derive(Debug)]
171−pub struct FileHunks<'a> {
178+pub struct Diff<'a> {
172− hunks: MutexGuard<'a, Vec<Hunk>>,
179+ diff: MutexGuard<'a, DiffInner>,
173 inverted: bool,
180 inverted: bool,
174}
181}
175
182
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+
177 pub fn is_inverted(&self) -> bool {
200 pub fn is_inverted(&self) -> bool {
178 self.inverted
201 self.inverted
179 }
202 }
@@ -181,7 +204,7 @@ impl FileHunks<'_> {
181 /// Returns the `Hunk` for the `n`th change in this file.
204 /// Returns the `Hunk` for the `n`th change in this file.
182 /// if there is no `n`th change `Hunk::NONE` is returned instead.
205 /// if there is no `n`th change `Hunk::NONE` is returned instead.
183 pub fn nth_hunk(&self, n: u32) -> Hunk {
206 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) {
185 Some(hunk) if self.inverted => hunk.invert(),
208 Some(hunk) if self.inverted => hunk.invert(),
186 Some(hunk) => hunk.clone(),
209 Some(hunk) => hunk.clone(),
187 None => Hunk::NONE,
210 None => Hunk::NONE,
@@ -189,7 +212,7 @@ impl FileHunks<'_> {
189 }
212 }
190
213
191 pub fn len(&self) -> u32 {
214 pub fn len(&self) -> u32 {
192− self.hunks.len() as u32
215+ self.diff.hunks.len() as u32
193 }
216 }
194
217
195 pub fn is_empty(&self) -> bool {
218 pub fn is_empty(&self) -> bool {
@@ -204,19 +227,20 @@ impl FileHunks<'_> {
204 };
227 };
205
228
206 let res = self
229 let res = self
230+ .diff
207 .hunks
231 .hunks
208 .binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
232 .binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
209
233
210 match res {
234 match res {
211 // Search found a hunk that starts exactly at this line, return the next hunk if it exists.
235 // 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,
213 Ok(pos) => Some(pos as u32 + 1),
237 Ok(pos) => Some(pos as u32 + 1),
214
238
215 // No hunk starts exactly at this line, so the search returns
239 // No hunk starts exactly at this line, so the search returns
216 // the position where a hunk starting at this line should be inserted.
240 // the position where a hunk starting at this line should be inserted.
217 // That position is exactly the position of the next hunk or the end
241 // That position is exactly the position of the next hunk or the end
218 // of the list if no such hunk exists
242 // 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,
220 Err(pos) => Some(pos as u32),
244 Err(pos) => Some(pos as u32),
221 }
245 }
222 }
246 }
@@ -228,6 +252,7 @@ impl FileHunks<'_> {
228 |hunk: &Hunk| hunk.after.clone()
252 |hunk: &Hunk| hunk.after.clone()
229 };
253 };
230 let res = self
254 let res = self
255+ .diff
231 .hunks
256 .hunks
232 .binary_search_by_key(&line, |hunk| hunk_range(hunk).end);
257 .binary_search_by_key(&line, |hunk| hunk_range(hunk).end);
233
258
@@ -237,7 +262,7 @@ impl FileHunks<'_> {
237 // which represents a pure removal.
262 // which represents a pure removal.
238 // Removals are technically empty but are still shown as single line hunks
263 // Removals are technically empty but are still shown as single line hunks
239 // and as such we must jump to the previous hunk (if it exists) if we are already inside the removal
264 // 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),
241
266
242 // No hunk ends exactly at this line, so the search returns
267 // No hunk ends exactly at this line, so the search returns
243 // the position where a hunk ending at this line should be inserted.
268 // the position where a hunk ending at this line should be inserted.
@@ -255,6 +280,7 @@ impl FileHunks<'_> {
255 };
280 };
256
281
257 let res = self
282 let res = self
283+ .diff
258 .hunks
284 .hunks
259 .binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
285 .binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
260
286
@@ -267,7 +293,7 @@ impl FileHunks<'_> {
267 // The previous hunk contains this hunk if it exists and doesn't end before this line
293 // The previous hunk contains this hunk if it exists and doesn't end before this line
268 Err(0) => None,
294 Err(0) => None,
269 Err(pos) => {
295 Err(pos) => {
270− let hunk = hunk_range(&self.hunks[pos - 1]);
296+ let hunk = hunk_range(&self.diff.hunks[pos - 1]);
271 if hunk.end > line || include_removal && hunk.start == line && hunk.is_empty() {
297 if hunk.end > line || include_removal && hunk.start == line && hunk.is_empty() {
272 Some(pos as u32 - 1)
298 Some(pos as u32 - 1)
273 } else {
299 } else {
helix-vcs/src/diff/line_cache.rs+8
@@ -43,6 +43,14 @@ impl InternedRopeLines {
43 res
43 res
44 }
44 }
45
45
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+
46 /// Updates the `diff_base` and optionally the document if `doc` is not None
54 /// Updates the `diff_base` and optionally the document if `doc` is not None
47 pub fn update_diff_base(&mut self, diff_base: Rope, doc: Option<Rope>) {
55 pub fn update_diff_base(&mut self, diff_base: Rope, doc: Option<Rope>) {
48 self.interned.clear();
56 self.interned.clear();
helix-vcs/src/diff/worker.rs+9−6
@@ -10,7 +10,7 @@ use tokio::sync::Notify;
10use tokio::time::{timeout, timeout_at, Duration};
10use tokio::time::{timeout, timeout_at, Duration};
11
11
12use crate::diff::{
12use 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,
14};
14};
15
15
16use super::line_cache::InternedRopeLines;
16use super::line_cache::InternedRopeLines;
@@ -21,7 +21,7 @@ mod test;
21
21
22pub(super) struct DiffWorker {
22pub(super) struct DiffWorker {
23 pub channel: UnboundedReceiver<Event>,
23 pub channel: UnboundedReceiver<Event>,
24− pub hunks: Arc<Mutex<Vec<Hunk>>>,
24+ pub diff: Arc<Mutex<DiffInner>>,
25 pub new_hunks: Vec<Hunk>,
25 pub new_hunks: Vec<Hunk>,
26 pub redraw_notify: Arc<Notify>,
26 pub redraw_notify: Arc<Notify>,
27 pub diff_finished_notify: Arc<Notify>,
27 pub diff_finished_notify: Arc<Notify>,
@@ -46,7 +46,7 @@ impl DiffWorker {
46 if let Some(lines) = interner.interned_lines() {
46 if let Some(lines) = interner.interned_lines() {
47 self.perform_diff(lines);
47 self.perform_diff(lines);
48 }
48 }
49− self.apply_hunks();
49+ self.apply_hunks(interner.diff_base(), interner.doc());
50 while let Some(event) = self.channel.recv().await {
50 while let Some(event) = self.channel.recv().await {
51 let (doc, diff_base) = self.accumulate_events(event).await;
51 let (doc, diff_base) = self.accumulate_events(event).await;
52
52
@@ -70,15 +70,18 @@ impl DiffWorker {
70 #[cfg(not(test))]
70 #[cfg(not(test))]
71 tokio::task::block_in_place(process_accumulated_events);
71 tokio::task::block_in_place(process_accumulated_events);
72
72
73− self.apply_hunks();
73+ self.apply_hunks(interner.diff_base(), interner.doc());
74 }
74 }
75 }
75 }
76
76
77 /// update the hunks (used by the gutter) by replacing it with `self.new_hunks`.
77 /// update the hunks (used by the gutter) by replacing it with `self.new_hunks`.
78 /// `self.new_hunks` is always empty after this function runs.
78 /// `self.new_hunks` is always empty after this function runs.
79 /// To improve performance this function tries to reuse the allocation of the old diff previously stored in `self.line_diffs`
79 /// 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) {
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;
81− swap(&mut *self.hunks.lock(), &mut self.new_hunks);
84+ swap(&mut diff.hunks, &mut self.new_hunks);
82 self.diff_finished_notify.notify_waiters();
85 self.diff_finished_notify.notify_waiters();
83 self.new_hunks.clear();
86 self.new_hunks.clear();
84 }
87 }
helix-vcs/src/diff/worker/test.rs+3−3
@@ -12,12 +12,12 @@ impl DiffHandle {
12 )
12 )
13 }
13 }
14 async fn into_diff(self, handle: JoinHandle<()>) -> Vec<Hunk> {
14 async fn into_diff(self, handle: JoinHandle<()>) -> Vec<Hunk> {
15− let hunks = self.hunks;
15+ let diff = self.diff;
16 // dropping the channel terminates the task
16 // dropping the channel terminates the task
17 drop(self.channel);
17 drop(self.channel);
18 handle.await.unwrap();
18 handle.await.unwrap();
19− let hunks = hunks.lock();
19+ let diff = diff.lock();
20− Vec::clone(&*hunks)
20+ Vec::clone(&diff.hunks)
21 }
21 }
22}
22}
23
23
helix-view/src/gutter.rs+1−1
@@ -100,7 +100,7 @@ pub fn diff<'doc>(
100 let deleted = theme.get("diff.minus");
100 let deleted = theme.get("diff.minus");
101 let modified = theme.get("diff.delta");
101 let modified = theme.get("diff.delta");
102 if let Some(diff_handle) = doc.diff_handle() {
102 if let Some(diff_handle) = doc.diff_handle() {
103− let hunks = diff_handle.hunks();
103+ let hunks = diff_handle.load();
104 let mut hunk_i = 0;
104 let mut hunk_i = 0;
105 let mut hunk = hunks.nth_hunk(hunk_i);
105 let mut hunk = hunks.nth_hunk(hunk_i);
106 Box::new(
106 Box::new(