Skip to content

Commit 9fd4fcf

Browse files
committed
Add a document level change picker
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.
1 parent cd20762 commit 9fd4fcf

File tree

3 files changed

+83
-5
lines changed

3 files changed

+83
-5
lines changed

helix-term/src/commands.rs

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ impl MappableCommand {
417417
jumplist_picker, "Open jumplist picker",
418418
symbol_picker, "Open symbol picker",
419419
changed_file_picker, "Open changed file picker",
420+
document_change_picker, "Open a picker of VCS changes in the current document",
420421
select_references_to_symbol_under_cursor, "Select symbol references",
421422
workspace_symbol_picker, "Open workspace symbol picker",
422423
diagnostics_picker, "Open diagnostic picker",
@@ -3261,6 +3262,78 @@ pub fn command_palette(cx: &mut Context) {
32613262
));
32623263
}
32633264

3265+
fn document_change_picker(cx: &mut Context) {
3266+
struct Data {
3267+
location: String,
3268+
style_added: Style,
3269+
style_modified: Style,
3270+
style_removed: Style,
3271+
}
3272+
3273+
let doc = doc!(cx.editor);
3274+
let Some(diff_handle) = doc.diff_handle() else {
3275+
cx.editor.set_status("Diff is not available in current buffer");
3276+
return;
3277+
};
3278+
let doc_id = doc.id();
3279+
let diff = diff_handle.load();
3280+
let location = doc
3281+
.relative_path()
3282+
.map(|path| path.to_string_lossy().into_owned())
3283+
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.to_string());
3284+
3285+
let data = Data {
3286+
location,
3287+
style_added: cx.editor.theme.get("diff.plus"),
3288+
style_modified: cx.editor.theme.get("diff.delta"),
3289+
style_removed: cx.editor.theme.get("diff.minus"),
3290+
};
3291+
3292+
let columns = vec![
3293+
PickerColumn::new("change", |hunk: &Hunk, data: &Data| {
3294+
use tui::text::Span;
3295+
3296+
if hunk.is_pure_insertion() {
3297+
Span::styled("+ added", data.style_added)
3298+
} else if hunk.is_pure_removal() {
3299+
Span::styled("- removed", data.style_removed)
3300+
} else {
3301+
Span::styled("~ modified", data.style_modified)
3302+
}
3303+
.into()
3304+
}),
3305+
PickerColumn::new("location", |hunk: &Hunk, data: &Data| {
3306+
format!("{}:{}", data.location, hunk.after.start).into()
3307+
}),
3308+
];
3309+
3310+
let hunks = diff.hunks().cloned().collect();
3311+
3312+
let picker = Picker::new(columns, 0, hunks, data, move |cx, hunk: &Hunk, action| {
3313+
cx.editor.switch(doc_id, action);
3314+
let config = cx.editor.config();
3315+
let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &doc_id));
3316+
let text = doc.text().slice(..);
3317+
let range = hunk_range(hunk, text);
3318+
doc.set_selection(view.id, range.into());
3319+
if action.align_view(view, doc.id()) {
3320+
view.ensure_cursor_in_view_center(doc, config.scrolloff)
3321+
}
3322+
})
3323+
.with_preview(move |_editor, hunk| {
3324+
let start_line = hunk.after.start as usize;
3325+
let end_line = if hunk.after.is_empty() {
3326+
start_line + 1
3327+
} else {
3328+
hunk.after.end as usize
3329+
};
3330+
Some((doc_id.into(), Some((start_line, end_line))))
3331+
});
3332+
3333+
drop(diff);
3334+
cx.push_layer(Box::new(overlaid(picker)));
3335+
}
3336+
32643337
fn last_picker(cx: &mut Context) {
32653338
// TODO: last picker does not seem to work well with buffer_picker
32663339
cx.callback.push(Box::new(|compositor, cx| {
@@ -3723,7 +3796,7 @@ fn goto_first_change_impl(cx: &mut Context, reverse: bool) {
37233796
diff.nth_hunk(idx)
37243797
};
37253798
if hunk != Hunk::NONE {
3726-
let range = hunk_range(hunk, doc.text().slice(..));
3799+
let range = hunk_range(&hunk, doc.text().slice(..));
37273800
doc.set_selection(view.id, Selection::single(range.anchor, range.head));
37283801
}
37293802
}
@@ -3765,7 +3838,7 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
37653838
return range;
37663839
};
37673840
let hunk = diff.nth_hunk(hunk_idx);
3768-
let new_range = hunk_range(hunk, doc_text);
3841+
let new_range = hunk_range(&hunk, doc_text);
37693842
if editor.mode == Mode::Select {
37703843
let head = if new_range.head < range.anchor {
37713844
new_range.anchor
@@ -3787,7 +3860,7 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
37873860
/// Returns the [Range] for a [Hunk] in the given text.
37883861
/// Additions and modifications cover the added and modified ranges.
37893862
/// Deletions are represented as the point at the start of the deletion hunk.
3790-
fn hunk_range(hunk: Hunk, text: RopeSlice) -> Range {
3863+
fn hunk_range(hunk: &Hunk, text: RopeSlice) -> Range {
37913864
let anchor = text.line_to_char(hunk.after.start as usize);
37923865
let head = if hunk.after.is_empty() {
37933866
anchor + 1

helix-term/src/keymap/default.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,11 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
223223
"S" => lsp_or_syntax_workspace_symbol_picker,
224224
"d" => diagnostics_picker,
225225
"D" => workspace_diagnostics_picker,
226-
"g" => changed_file_picker,
226+
"g" => document_change_picker,
227+
"G" => changed_file_picker,
227228
"a" => code_action,
228229
"'" => last_picker,
229-
"G" => { "Debug (experimental)" sticky=true
230+
"e" => { "Debug (experimental)" sticky=true
230231
"l" => dap_launch,
231232
"r" => dap_restart,
232233
"b" => dap_toggle_breakpoint,

helix-vcs/src/diff.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,8 @@ impl Diff<'_> {
289289
}
290290
}
291291
}
292+
293+
pub fn hunks(&self) -> impl Iterator<Item = &Hunk> {
294+
self.diff.hunks.iter()
295+
}
292296
}

0 commit comments

Comments
 (0)