mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 01:16:18 +04:00
Add command for resetting diff hunks (#5736)
This commit is contained in:
parent
8c2e447b16
commit
48b6aa9a69
@ -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. |
|
||||
|
@ -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);
|
||||
|
@ -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 @@ fn run_shell_command(
|
||||
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>> =
|
||||
|
@ -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 @@ fn new_with_handle(
|
||||
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 @@ fn new_with_handle(
|
||||
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 @@ pub fn invert(&mut self) {
|
||||
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 @@ pub fn is_pure_removal(&self) -> bool {
|
||||
/// 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 @@ pub fn is_inverted(&self) -> bool {
|
||||
/// 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 @@ pub fn nth_hunk(&self, n: u32) -> Hunk {
|
||||
}
|
||||
|
||||
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 @@ pub fn next_hunk(&self, line: u32) -> Option<u32> {
|
||||
};
|
||||
|
||||
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 @@ pub fn prev_hunk(&self, line: u32) -> Option<u32> {
|
||||
|hunk: &Hunk| hunk.after.clone()
|
||||
};
|
||||
let res = self
|
||||
.diff
|
||||
.hunks
|
||||
.binary_search_by_key(&line, |hunk| hunk_range(hunk).end);
|
||||
|
||||
@ -237,7 +262,7 @@ pub fn prev_hunk(&self, line: u32) -> Option<u32> {
|
||||
// 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 @@ pub fn hunk_at(&self, line: u32, include_removal: bool) -> Option<u32> {
|
||||
};
|
||||
|
||||
let res = self
|
||||
.diff
|
||||
.hunks
|
||||
.binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
|
||||
|
||||
@ -267,7 +293,7 @@ pub fn hunk_at(&self, line: u32, include_removal: bool) -> Option<u32> {
|
||||
// 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 {
|
||||
|
@ -43,6 +43,14 @@ pub fn new(diff_base: Rope, doc: Rope) -> 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();
|
||||
|
@ -10,7 +10,7 @@
|
||||
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 @@
|
||||
|
||||
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 @@ pub async fn run(mut self, diff_base: Rope, doc: Rope) {
|
||||
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 @@ pub async fn run(mut self, diff_base: Rope, doc: Rope) {
|
||||
#[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();
|
||||
}
|
||||
|
@ -12,12 +12,12 @@ fn new_test(diff_base: &str, doc: &str) -> (DiffHandle, JoinHandle<()>) {
|
||||
)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user