Skip to content

Commit

Permalink
Add a document level change picker
Browse files Browse the repository at this point in the history
This is an experiment with some of the ideas in issue 5362. Just trying
it out to see how often I use it and if I find it very useful.
  • Loading branch information
the-mikedavis committed Mar 30, 2024
1 parent cd20762 commit 9fd4fcf
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 5 deletions.
79 changes: 76 additions & 3 deletions helix-term/src/commands.rs
Expand Up @@ -417,6 +417,7 @@ impl MappableCommand {
jumplist_picker, "Open jumplist picker",
symbol_picker, "Open symbol picker",
changed_file_picker, "Open changed file picker",
document_change_picker, "Open a picker of VCS changes in the current document",
select_references_to_symbol_under_cursor, "Select symbol references",
workspace_symbol_picker, "Open workspace symbol picker",
diagnostics_picker, "Open diagnostic picker",
Expand Down Expand Up @@ -3261,6 +3262,78 @@ pub fn command_palette(cx: &mut Context) {
));
}

fn document_change_picker(cx: &mut Context) {
struct Data {
location: String,
style_added: Style,
style_modified: Style,
style_removed: Style,
}

let doc = doc!(cx.editor);
let Some(diff_handle) = doc.diff_handle() else {
cx.editor.set_status("Diff is not available in current buffer");
return;
};
let doc_id = doc.id();
let diff = diff_handle.load();
let location = doc
.relative_path()
.map(|path| path.to_string_lossy().into_owned())
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.to_string());

let data = Data {
location,
style_added: cx.editor.theme.get("diff.plus"),
style_modified: cx.editor.theme.get("diff.delta"),
style_removed: cx.editor.theme.get("diff.minus"),
};

let columns = vec![
PickerColumn::new("change", |hunk: &Hunk, data: &Data| {
use tui::text::Span;

if hunk.is_pure_insertion() {
Span::styled("+ added", data.style_added)
} else if hunk.is_pure_removal() {
Span::styled("- removed", data.style_removed)
} else {
Span::styled("~ modified", data.style_modified)
}
.into()
}),
PickerColumn::new("location", |hunk: &Hunk, data: &Data| {
format!("{}:{}", data.location, hunk.after.start).into()
}),
];

let hunks = diff.hunks().cloned().collect();

let picker = Picker::new(columns, 0, hunks, data, move |cx, hunk: &Hunk, action| {
cx.editor.switch(doc_id, action);
let config = cx.editor.config();
let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &doc_id));
let text = doc.text().slice(..);
let range = hunk_range(hunk, text);
doc.set_selection(view.id, range.into());
if action.align_view(view, doc.id()) {
view.ensure_cursor_in_view_center(doc, config.scrolloff)
}
})
.with_preview(move |_editor, hunk| {
let start_line = hunk.after.start as usize;
let end_line = if hunk.after.is_empty() {
start_line + 1
} else {
hunk.after.end as usize
};
Some((doc_id.into(), Some((start_line, end_line))))
});

drop(diff);
cx.push_layer(Box::new(overlaid(picker)));
}

fn last_picker(cx: &mut Context) {
// TODO: last picker does not seem to work well with buffer_picker
cx.callback.push(Box::new(|compositor, cx| {
Expand Down Expand Up @@ -3723,7 +3796,7 @@ fn goto_first_change_impl(cx: &mut Context, reverse: bool) {
diff.nth_hunk(idx)
};
if hunk != Hunk::NONE {
let range = hunk_range(hunk, doc.text().slice(..));
let range = hunk_range(&hunk, doc.text().slice(..));
doc.set_selection(view.id, Selection::single(range.anchor, range.head));
}
}
Expand Down Expand Up @@ -3765,7 +3838,7 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
return range;
};
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 {
let head = if new_range.head < range.anchor {
new_range.anchor
Expand All @@ -3787,7 +3860,7 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
/// Returns the [Range] for a [Hunk] in the given text.
/// Additions and modifications cover the added and modified ranges.
/// Deletions are represented as the point at the start of the deletion hunk.
fn hunk_range(hunk: Hunk, text: RopeSlice) -> Range {
fn hunk_range(hunk: &Hunk, text: RopeSlice) -> Range {
let anchor = text.line_to_char(hunk.after.start as usize);
let head = if hunk.after.is_empty() {
anchor + 1
Expand Down
5 changes: 3 additions & 2 deletions helix-term/src/keymap/default.rs
Expand Up @@ -223,10 +223,11 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"S" => lsp_or_syntax_workspace_symbol_picker,
"d" => diagnostics_picker,
"D" => workspace_diagnostics_picker,
"g" => changed_file_picker,
"g" => document_change_picker,
"G" => changed_file_picker,
"a" => code_action,
"'" => last_picker,
"G" => { "Debug (experimental)" sticky=true
"e" => { "Debug (experimental)" sticky=true
"l" => dap_launch,
"r" => dap_restart,
"b" => dap_toggle_breakpoint,
Expand Down
4 changes: 4 additions & 0 deletions helix-vcs/src/diff.rs
Expand Up @@ -289,4 +289,8 @@ impl Diff<'_> {
}
}
}

pub fn hunks(&self) -> impl Iterator<Item = &Hunk> {
self.diff.hunks.iter()
}
}

0 comments on commit 9fd4fcf

Please sign in to comment.