Skip to content

Commit 184bc62

Browse files
committed
feat: Add command and bindings to list changes from VCS in opened document(s)
1 parent 3f0b07f commit 184bc62

File tree

3 files changed

+167
-1
lines changed

3 files changed

+167
-1
lines changed

helix-term/src/commands.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
pub(crate) mod dap;
22
pub(crate) mod lsp;
33
pub(crate) mod typed;
4+
pub(crate) mod vcs;
45

56
pub use dap::*;
67
use helix_vcs::Hunk;
78
pub use lsp::*;
89
use tui::widgets::Row;
910
pub use typed::*;
11+
pub use vcs::*;
1012

1113
use helix_core::{
1214
char_idx_at_visual_offset, comment,
@@ -77,7 +79,6 @@ pub struct Context<'a> {
7779
pub register: Option<char>,
7880
pub count: Option<NonZeroUsize>,
7981
pub editor: &'a mut Editor,
80-
8182
pub callback: Option<crate::compositor::Callback>,
8283
pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
8384
pub jobs: &'a mut Jobs,
@@ -286,6 +287,7 @@ impl MappableCommand {
286287
workspace_symbol_picker, "Open workspace symbol picker",
287288
diagnostics_picker, "Open diagnostic picker",
288289
workspace_diagnostics_picker, "Open workspace diagnostic picker",
290+
vcs_change_picker, "Open VCS changes picker",
289291
last_picker, "Open last picker",
290292
insert_at_line_start, "Insert at start of line",
291293
insert_at_line_end, "Insert at end of line",

helix-term/src/commands/vcs.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use std::{ops::Range, path::PathBuf};
2+
3+
use helix_core::Selection;
4+
use helix_lsp::Url;
5+
use helix_view::theme::Style;
6+
use helix_view::Document;
7+
use tui::widgets::{Cell, Row};
8+
9+
use super::{align_view, push_jump, Align, Context, SourcePathFormat};
10+
use crate::ui::{menu::Item, FilePicker};
11+
12+
/// Picker to list the VCS changes in the current document
13+
pub fn vcs_change_picker(cx: &mut Context) {
14+
let current_doc = doc!(cx.editor);
15+
16+
let mut changes = Vec::new();
17+
add_changes_for_doc(&mut changes, current_doc);
18+
19+
let picker = vcs_picker(cx, changes, current_doc.url(), SourcePathFormat::Hide);
20+
cx.push_layer(Box::new(picker));
21+
}
22+
23+
// TODO: once both #5472 and #5645 are merged, do a workspace-changes picker
24+
25+
fn add_changes_for_doc(changes: &mut Vec<DiffBlock>, doc: &Document) {
26+
let url = match doc.url() {
27+
Some(url) => url,
28+
None => return,
29+
};
30+
31+
if let Some(diff_handle) = doc.diff_handle() {
32+
let file_hunks = diff_handle.hunks();
33+
if file_hunks.is_empty() {
34+
return;
35+
}
36+
37+
changes.reserve(file_hunks.len() as _);
38+
39+
for i in 0..file_hunks.len() {
40+
let hunk = file_hunks.nth_hunk(i);
41+
42+
let ty = if hunk.is_pure_insertion() {
43+
DiffType::PureInsertion
44+
} else if hunk.is_pure_removal() {
45+
DiffType::PureRemoval
46+
} else {
47+
DiffType::Delta
48+
};
49+
50+
let range = hunk.after;
51+
let line = doc
52+
.text()
53+
.get_line(range.start as usize)
54+
.map_or_else(Default::default, |l| l.to_string());
55+
56+
changes.push(DiffBlock {
57+
url: url.clone(),
58+
ty,
59+
line,
60+
range,
61+
});
62+
}
63+
}
64+
}
65+
66+
struct DiffStyles {
67+
insertion: Style,
68+
removal: Style,
69+
delta: Style,
70+
}
71+
72+
struct DiffBlock {
73+
url: Url,
74+
ty: DiffType,
75+
line: String,
76+
range: Range<u32>,
77+
}
78+
79+
#[derive(Debug, Copy, Clone)]
80+
enum DiffType {
81+
PureInsertion,
82+
PureRemoval,
83+
Delta,
84+
}
85+
86+
impl DiffType {
87+
fn as_str(&self) -> &'static str {
88+
match self {
89+
DiffType::PureInsertion => "[+]",
90+
DiffType::PureRemoval => "[-]",
91+
DiffType::Delta => "[~]",
92+
}
93+
}
94+
}
95+
96+
impl Item for DiffBlock {
97+
type Data = (DiffStyles, SourcePathFormat);
98+
99+
fn format(&self, (styles, source_path_format): &Self::Data) -> Row {
100+
let path = match source_path_format {
101+
SourcePathFormat::Hide => String::new(),
102+
SourcePathFormat::Show => {
103+
let path = helix_core::path::get_truncated_path(self.url.path());
104+
format!("{}:", path.to_string_lossy())
105+
}
106+
};
107+
108+
let diff_style = match self.ty {
109+
DiffType::PureInsertion => styles.insertion,
110+
DiffType::PureRemoval => styles.removal,
111+
DiffType::Delta => styles.delta,
112+
};
113+
114+
Row::new([
115+
Cell::from(path),
116+
Cell::from(self.ty.as_str()).style(diff_style),
117+
Cell::from(self.line.as_str()),
118+
])
119+
}
120+
}
121+
122+
fn vcs_picker(
123+
cx: &Context,
124+
changes: Vec<DiffBlock>,
125+
current_path: Option<Url>,
126+
show_source_path: SourcePathFormat,
127+
) -> FilePicker<DiffBlock> {
128+
let styles = DiffStyles {
129+
insertion: cx.editor.theme.get("diff.plus"),
130+
removal: cx.editor.theme.get("diff.minus"),
131+
delta: cx.editor.theme.get("diff.delta"),
132+
};
133+
134+
FilePicker::new(
135+
changes,
136+
(styles, show_source_path),
137+
move |cx, DiffBlock { url, range, .. }, action| {
138+
if current_path.as_ref() == Some(url) {
139+
let (view, doc) = current!(cx.editor);
140+
push_jump(view, doc);
141+
} else {
142+
let path = url.to_file_path().unwrap();
143+
cx.editor.open(&path, action).expect("editor.open failed");
144+
}
145+
146+
let (view, doc) = current!(cx.editor);
147+
148+
let anchor = doc.text().line_to_char(range.start as usize);
149+
let head = doc
150+
.text()
151+
.line_to_char(range.end as usize)
152+
.saturating_sub(1);
153+
doc.set_selection(view.id, Selection::single(anchor, head));
154+
align_view(doc, view, Align::Center);
155+
},
156+
move |_editor, DiffBlock { url, range, .. }| {
157+
Some((
158+
PathBuf::from(url.path()).into(),
159+
Some((range.start as usize, range.end.saturating_sub(1) as usize)),
160+
))
161+
},
162+
)
163+
}

helix-term/src/keymap/default.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ pub fn default() -> HashMap<Mode, Keymap> {
219219
"S" => workspace_symbol_picker,
220220
"d" => diagnostics_picker,
221221
"D" => workspace_diagnostics_picker,
222+
"v" => vcs_change_picker,
222223
"a" => code_action,
223224
"'" => last_picker,
224225
"g" => { "Debug (experimental)" sticky=true

0 commit comments

Comments
 (0)