Skip to content

Commit 1c9e486

Browse files
committed
Show git diff signs in gutter
1 parent a449156 commit 1c9e486

File tree

11 files changed

+242
-4
lines changed

11 files changed

+242
-4
lines changed

Cargo.lock

+69
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ members = [
77
"helix-syntax",
88
"helix-lsp",
99
"helix-dap",
10+
"helix-vcs",
1011
"xtask",
1112
]
1213

helix-term/src/commands.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -2146,6 +2146,8 @@ pub mod cmd {
21462146
if doc.path().is_none() {
21472147
bail!("cannot write a buffer without a filename");
21482148
}
2149+
let id = doc.id();
2150+
21492151
let fmt = doc.auto_format().map(|fmt| {
21502152
let shared = fmt.shared();
21512153
let callback = make_format_callback(
@@ -2157,11 +2159,17 @@ pub mod cmd {
21572159
jobs.callback(callback);
21582160
shared
21592161
});
2160-
let future = doc.format_and_save(fmt);
2161-
cx.jobs.add(Job::new(future).wait_before_exiting());
2162+
let future = doc.format_and_save(fmt).map(move |_| {
2163+
let call: job::Callback =
2164+
Box::new(move |editor: &mut Editor, _compositor: &mut Compositor| {
2165+
let _ = editor.refresh_version_control(id);
2166+
});
2167+
Ok(call)
2168+
});
2169+
cx.jobs
2170+
.add(Job::with_callback(future).wait_before_exiting());
21622171

21632172
if path.is_some() {
2164-
let id = doc.id();
21652173
let _ = cx.editor.refresh_language_server(id);
21662174
}
21672175
Ok(())

helix-vcs/Cargo.toml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "helix-vcs"
3+
version = "0.6.0"
4+
authors = ["Blaž Hrastnik <[email protected]>"]
5+
edition = "2021"
6+
license = "MPL-2.0"
7+
categories = ["editor"]
8+
repository = "https://github.com/helix-editor/helix"
9+
homepage = "https://helix-editor.com"
10+
11+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12+
13+
[dependencies]
14+
git2 = { version = "0.13", default-features = false }

helix-vcs/src/git.rs

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use std::collections::HashMap;
2+
use std::path::{Path, PathBuf};
3+
4+
use git2::{DiffDelta, DiffHunk, DiffOptions, Repository};
5+
6+
use crate::{LineDiff, LineDiffs};
7+
8+
pub struct Git {
9+
repo: Repository,
10+
root: PathBuf,
11+
}
12+
13+
impl std::fmt::Debug for Git {
14+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15+
f.debug_struct("Git").field("root", &self.root).finish()
16+
}
17+
}
18+
19+
impl Git {
20+
pub fn discover_from_path(file: &Path) -> Option<Self> {
21+
let repo = Repository::discover(file).ok()?;
22+
let root = repo.workdir()?.to_path_buf();
23+
Some(Self { repo, root })
24+
}
25+
26+
// Modified from https://github.com/sharkdp/bat/blob/master/src/diff.rs
27+
pub fn line_diffs(&mut self, file: &Path) -> Option<LineDiffs> {
28+
let mut diff_options = DiffOptions::new();
29+
let relative = file.strip_prefix(&self.root).ok()?;
30+
diff_options.pathspec(relative);
31+
diff_options.context_lines(0);
32+
33+
let diff = self
34+
.repo
35+
.diff_index_to_workdir(None, Some(&mut diff_options))
36+
.ok()?;
37+
38+
let mut line_diffs: LineDiffs = HashMap::new();
39+
40+
let mut mark_section = |start: u32, end: u32, change: LineDiff| {
41+
for line in start..=end {
42+
line_diffs.insert(line as usize, change);
43+
}
44+
};
45+
46+
let hunk_cb = &mut |delta: DiffDelta, hunk: DiffHunk| {
47+
let relative_path = delta.new_file().path().unwrap_or_else(|| Path::new(""));
48+
if !file.ends_with(relative_path) {
49+
return false;
50+
}
51+
52+
let old_lines = hunk.old_lines();
53+
let new_start = hunk.new_start();
54+
let new_lines = hunk.new_lines();
55+
let new_end = new_start + new_lines.saturating_sub(1);
56+
57+
if old_lines == 0 && new_lines > 0 {
58+
mark_section(new_start, new_end, LineDiff::Added);
59+
} else if new_lines == 0 && old_lines > 0 {
60+
if new_start == 0 {
61+
mark_section(1, 1, LineDiff::DeletedAbove);
62+
} else {
63+
mark_section(new_start, new_start, LineDiff::Deleted);
64+
}
65+
} else {
66+
// TODO: modified and added lines nearby render as big modified chunk
67+
mark_section(new_start, new_end, LineDiff::Modified);
68+
}
69+
70+
true
71+
};
72+
73+
diff.foreach(&mut |_, _| true, None, Some(hunk_cb), None)
74+
.ok()?;
75+
76+
Some(line_diffs)
77+
}
78+
}

helix-vcs/src/lib.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use std::collections::HashMap;
2+
3+
mod git;
4+
5+
#[derive(Copy, Clone, Debug)]
6+
pub enum LineDiff {
7+
Added,
8+
Deleted,
9+
DeletedAbove,
10+
Modified,
11+
}
12+
13+
/// Maps line numbers to changes
14+
pub type LineDiffs = HashMap<usize, LineDiff>;
15+
16+
pub use git::Git;

helix-view/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ anyhow = "1"
1919
helix-core = { version = "0.6", path = "../helix-core" }
2020
helix-lsp = { version = "0.6", path = "../helix-lsp"}
2121
helix-dap = { version = "0.6", path = "../helix-dap"}
22+
helix-vcs = { version = "0.6", path = "../helix-vcs"}
2223
crossterm = { version = "0.23", optional = true }
2324

2425
# Conversion traits

helix-view/src/document.rs

+11
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ pub struct Document {
118118
pub(crate) modified_since_accessed: bool,
119119

120120
diagnostics: Vec<Diagnostic>,
121+
line_diffs: helix_vcs::LineDiffs,
121122
language_server: Option<Arc<helix_lsp::Client>>,
122123
}
123124

@@ -142,6 +143,7 @@ impl fmt::Debug for Document {
142143
.field("modified_since_accessed", &self.modified_since_accessed)
143144
.field("diagnostics", &self.diagnostics)
144145
// .field("language_server", &self.language_server)
146+
.field("line_diffs", &self.line_diffs)
145147
.finish()
146148
}
147149
}
@@ -359,6 +361,7 @@ impl Document {
359361
last_saved_revision: 0,
360362
modified_since_accessed: false,
361363
language_server: None,
364+
line_diffs: helix_vcs::LineDiffs::new(),
362365
}
363366
}
364367

@@ -946,6 +949,14 @@ impl Document {
946949
self.diagnostics
947950
.sort_unstable_by_key(|diagnostic| diagnostic.range);
948951
}
952+
953+
pub fn line_diffs(&self) -> &helix_vcs::LineDiffs {
954+
&self.line_diffs
955+
}
956+
957+
pub fn set_line_diffs(&mut self, line_diffs: helix_vcs::LineDiffs) {
958+
self.line_diffs = line_diffs;
959+
}
949960
}
950961

951962
impl Default for Document {

helix-view/src/editor.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ pub struct Editor {
280280
pub debugger_events: SelectAll<UnboundedReceiverStream<dap::Payload>>,
281281
pub breakpoints: HashMap<PathBuf, Vec<Breakpoint>>,
282282

283+
pub version_control: Option<helix_vcs::Git>,
283284
pub clipboard_provider: Box<dyn ClipboardProvider>,
284285

285286
pub syn_loader: Arc<syntax::Loader>,
@@ -312,6 +313,8 @@ impl Editor {
312313
config: Config,
313314
) -> Self {
314315
let language_servers = helix_lsp::Registry::new();
316+
let version_control =
317+
helix_core::find_root(None, &[]).and_then(|p| helix_vcs::Git::discover_from_path(&p));
315318

316319
// HAXX: offset the render area height by 1 to account for prompt/commandline
317320
area.height -= 1;
@@ -328,6 +331,7 @@ impl Editor {
328331
debugger: None,
329332
debugger_events: SelectAll::new(),
330333
breakpoints: HashMap::new(),
334+
version_control,
331335
syn_loader,
332336
theme_loader,
333337
registers: Registers::default(),
@@ -382,6 +386,13 @@ impl Editor {
382386
self._refresh();
383387
}
384388

389+
pub fn refresh_version_control(&mut self, doc_id: DocumentId) -> Option<()> {
390+
let doc = self.documents.get_mut(&doc_id)?;
391+
let diffs = self.version_control.as_mut()?.line_diffs(doc.path()?)?;
392+
doc.set_line_diffs(diffs);
393+
Some(())
394+
}
395+
385396
/// Refreshes the language server for a given document
386397
pub fn refresh_language_server(&mut self, doc_id: DocumentId) -> Option<()> {
387398
let doc = self.documents.get_mut(&doc_id)?;
@@ -567,8 +578,10 @@ impl Editor {
567578
let mut doc = Document::open(&path, None, Some(self.syn_loader.clone()))?;
568579

569580
let _ = Self::launch_language_server(&mut self.language_servers, &mut doc);
581+
let id = self.new_document(doc);
582+
let _ = self.refresh_version_control(id);
570583

571-
self.new_document(doc)
584+
id
572585
};
573586

574587
self.switch(id, action);

0 commit comments

Comments
 (0)