mirror of https://github.com/helix-editor/helix
Merge 587ec24b04
into 362e97e927
commit
ee651adfbc
|
@ -82,6 +82,8 @@
|
|||
| `: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. |
|
||||
| `:show-selection-diff-popup`, `:diffshow` | Show a popup with the unsaved diff hunks intersecting the primary selection. |
|
||||
| `:yank-selection-diff`, `:diffyank` | Yank the unsaved diff hunks intersecting the primary selection. |
|
||||
| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
|
||||
| `:redraw` | Clear and re-render the whole UI |
|
||||
| `:move`, `:mv` | Move the current buffer and its corresponding file to a different path |
|
||||
|
|
|
@ -168,6 +168,21 @@ pub fn compare_ropes(before: &Rope, after: &Rope) -> Transaction {
|
|||
res
|
||||
}
|
||||
|
||||
/// Compares `old` and `new` to generate a text diff
|
||||
pub fn diff_ropes(before: RopeSlice, after: RopeSlice) -> String {
|
||||
let before = before.to_string();
|
||||
let after = after.to_string();
|
||||
let input = InternedInput::new(before.as_str(), after.as_str());
|
||||
let mut diff = Diff::compute(Algorithm::Histogram, &input);
|
||||
diff.postprocess_lines(&input);
|
||||
diff.unified_diff(
|
||||
&imara_diff::BasicLineDiffPrinter(&input.interner),
|
||||
imara_diff::UnifiedDiffConfig::default(),
|
||||
&input,
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::job::Job;
|
|||
use super::*;
|
||||
|
||||
use helix_core::command_line::{Args, Flag, Signature, Token, TokenKind};
|
||||
use helix_core::diff::diff_ropes;
|
||||
use helix_core::fuzzy::fuzzy_match;
|
||||
use helix_core::indent::MAX_INDENT;
|
||||
use helix_core::line_ending;
|
||||
|
@ -2406,6 +2407,104 @@ fn reset_diff_change(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn get_diff_change_at_selection(editor: &mut Editor) -> anyhow::Result<String> {
|
||||
let (view, doc) = current!(editor);
|
||||
let Some(handle) = doc.diff_handle() else {
|
||||
bail!("Diff is not available in the current buffer")
|
||||
};
|
||||
|
||||
let diff = handle.load();
|
||||
let doc_text = doc.text().slice(..);
|
||||
let primary_selection = doc.selection(view.id).primary();
|
||||
|
||||
let Some((base_start, base_end, doc_start, doc_end)) = diff
|
||||
.hunks_intersecting_line_ranges([primary_selection.line_range(doc_text)].into_iter())
|
||||
.fold(None, |line_ranges, hunk| match line_ranges {
|
||||
Some((base_start, base_end, doc_start, doc_end)) => Some((
|
||||
hunk.before.start.min(base_start),
|
||||
hunk.before.end.max(base_end),
|
||||
hunk.after.start.min(doc_start),
|
||||
hunk.after.end.max(doc_end),
|
||||
)),
|
||||
None => Some((
|
||||
hunk.before.start,
|
||||
hunk.before.end,
|
||||
hunk.after.start,
|
||||
hunk.after.end,
|
||||
)),
|
||||
})
|
||||
else {
|
||||
bail!("There are no changes in the primary selection");
|
||||
};
|
||||
|
||||
let base = diff.diff_base();
|
||||
let doc = diff.doc();
|
||||
Ok(diff_ropes(
|
||||
base.slice(
|
||||
base.line_to_char(base_start as usize)..base.line_to_char((base_end as usize) + 1) - 1,
|
||||
),
|
||||
doc.slice(
|
||||
doc.line_to_char(doc_start as usize)..doc.line_to_char((doc_end as usize) + 1) - 1,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn show_selection_diff_popup(
|
||||
cx: &mut compositor::Context,
|
||||
args: Args,
|
||||
event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
if event != PromptEvent::Validate {
|
||||
return Ok(());
|
||||
}
|
||||
ensure!(
|
||||
args.is_empty(),
|
||||
"show-selection-diff-popup takes no arguments"
|
||||
);
|
||||
|
||||
let text = get_diff_change_at_selection(cx.editor)?;
|
||||
|
||||
let callback = async move {
|
||||
let call: job::Callback = Callback::EditorCompositor(Box::new(
|
||||
move |editor: &mut Editor, compositor: &mut Compositor| {
|
||||
let contents =
|
||||
ui::Markdown::new(format!("```diff\n{}```", text), editor.syn_loader.clone());
|
||||
let popup = Popup::new("show-selection-diff-popup", contents).auto_close(true);
|
||||
compositor.replace_or_push("show-selection-diff-popup", popup);
|
||||
},
|
||||
));
|
||||
Ok(call)
|
||||
};
|
||||
cx.jobs.callback(callback);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn yank_selection_diff(
|
||||
cx: &mut compositor::Context,
|
||||
args: Args,
|
||||
event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
if event != PromptEvent::Validate {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let reg = match args.first() {
|
||||
Some(s) => {
|
||||
ensure!(s.chars().count() == 1, format!("Invalid register {s}"));
|
||||
s.chars().next().unwrap()
|
||||
}
|
||||
None => '+',
|
||||
};
|
||||
|
||||
let text = get_diff_change_at_selection(cx.editor)?;
|
||||
|
||||
cx.editor.registers.write(reg, vec![text])?;
|
||||
cx.editor.set_status(format!(
|
||||
"Yanked diff changes in the primary selection to register {reg}",
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_register(
|
||||
cx: &mut compositor::Context,
|
||||
args: Args,
|
||||
|
@ -3490,6 +3589,28 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
|
|||
..Signature::DEFAULT
|
||||
},
|
||||
},
|
||||
TypableCommand {
|
||||
name: "show-selection-diff-popup",
|
||||
aliases: &["diffshow"],
|
||||
doc: "Show a popup with the unsaved diff hunks intersecting the primary selection.",
|
||||
fun: show_selection_diff_popup,
|
||||
completer: CommandCompleter::all(completers::register),
|
||||
signature: Signature {
|
||||
positionals: (0, Some(0)),
|
||||
..Signature::DEFAULT
|
||||
},
|
||||
},
|
||||
TypableCommand {
|
||||
name: "yank-selection-diff",
|
||||
aliases: &["diffyank"],
|
||||
doc: "Yank the unsaved diff hunks intersecting the primary selection.",
|
||||
fun: yank_selection_diff,
|
||||
completer: CommandCompleter::all(completers::register),
|
||||
signature: Signature {
|
||||
positionals: (0, Some(1)),
|
||||
..Signature::DEFAULT
|
||||
},
|
||||
},
|
||||
TypableCommand {
|
||||
name: "clear-register",
|
||||
aliases: &[],
|
||||
|
|
Loading…
Reference in New Issue