on master help sign in
Add a version-control statusline element (#5682)
1661e4bDimitar Gyurov
Cargo.lock+1
@@ -1212,6 +1212,7 @@ dependencies = [
1212name = "helix-vcs"
1212name = "helix-vcs"
1213version = "0.6.0"
1213version = "0.6.0"
1214dependencies = [
1214dependencies = [
1215+ "arc-swap",
1215 "gix",
1216 "gix",
1216 "helix-core",
1217 "helix-core",
1217 "imara-diff",
1218 "imara-diff",
book/src/configuration.md+1
@@ -111,6 +111,7 @@ The following statusline elements can be configured:
111| `position-percentage` | The cursor position as a percentage of the total number of lines |
111| `position-percentage` | The cursor position as a percentage of the total number of lines |
112| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
112| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
113| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
113| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
114+| `version-control` | The current branch name or detached commit hash of the opened workspace |
114
115
115### `[editor.lsp]` Section
116### `[editor.lsp]` Section
116
117
helix-term/src/ui/statusline.rs+14
@@ -159,6 +159,7 @@ where
159 helix_view::editor::StatusLineElement::TotalLineNumbers => render_total_line_numbers,
159 helix_view::editor::StatusLineElement::TotalLineNumbers => render_total_line_numbers,
160 helix_view::editor::StatusLineElement::Separator => render_separator,
160 helix_view::editor::StatusLineElement::Separator => render_separator,
161 helix_view::editor::StatusLineElement::Spacer => render_spacer,
161 helix_view::editor::StatusLineElement::Spacer => render_spacer,
162+ helix_view::editor::StatusLineElement::VersionControl => render_version_control,
162 }
163 }
163}
164}
164
165
@@ -476,3 +477,16 @@ where
476{
477{
477 write(context, String::from(" "), None);
478 write(context, String::from(" "), None);
478}
479}
480+
481+fn render_version_control<F>(context: &mut RenderContext, write: F)
482+where
483+ F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
484+{
485+ let head = context
486+ .doc
487+ .version_control_head()
488+ .unwrap_or_default()
489+ .to_string();
490+
491+ write(context, head, None);
492+}
helix-vcs/Cargo.toml+1
@@ -15,6 +15,7 @@ helix-core = { version = "0.6", path = "../helix-core" }
15
15
16tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] }
16tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] }
17parking_lot = "0.12"
17parking_lot = "0.12"
18+arc-swap = { version = "1.6.0" }
18
19
19gix = { version = "0.39.0", default-features = false , optional = true }
20gix = { version = "0.39.0", default-features = false , optional = true }
20imara-diff = "0.1.5"
21imara-diff = "0.1.5"
helix-vcs/src/git.rs+17
@@ -1,4 +1,6 @@
1+use arc_swap::ArcSwap;
1use std::path::Path;
2use std::path::Path;
3+use std::sync::Arc;
2
4
3use gix::objs::tree::EntryMode;
5use gix::objs::tree::EntryMode;
4use gix::sec::trust::DefaultForLevel;
6use gix::sec::trust::DefaultForLevel;
@@ -87,6 +89,21 @@ impl DiffProvider for Git {
87 }
89 }
88 Some(data)
90 Some(data)
89 }
91 }
92+
93+ fn get_current_head_name(&self, file: &Path) -> Option<Arc<ArcSwap<Box<str>>>> {
94+ debug_assert!(!file.exists() || file.is_file());
95+ debug_assert!(file.is_absolute());
96+ let repo = Git::open_repo(file.parent()?, None)?.to_thread_local();
97+ let head_ref = repo.head_ref().ok()?;
98+ let head_commit = repo.head_commit().ok()?;
99+
100+ let name = match head_ref {
101+ Some(reference) => reference.name().shorten().to_string(),
102+ None => head_commit.id.to_hex_with_len(8).to_string(),
103+ };
104+
105+ Some(Arc::new(ArcSwap::from_pointee(name.into_boxed_str())))
106+ }
90}
107}
91
108
92/// Finds the object that contains the contents of a file at a specific commit.
109/// Finds the object that contains the contents of a file at a specific commit.
helix-vcs/src/lib.rs+13−1
@@ -1,4 +1,5 @@
1+use arc_swap::ArcSwap;
1−use std::path::Path;
2+use std::{path::Path, sync::Arc};
2
3
3#[cfg(feature = "git")]
4#[cfg(feature = "git")]
4pub use git::Git;
5pub use git::Git;
@@ -18,6 +19,7 @@ pub trait DiffProvider {
18 /// The data is returned as raw byte without any decoding or encoding performed
19 /// The data is returned as raw byte without any decoding or encoding performed
19 /// to ensure all file encodings are handled correctly.
20 /// to ensure all file encodings are handled correctly.
20 fn get_diff_base(&self, file: &Path) -> Option<Vec<u8>>;
21 fn get_diff_base(&self, file: &Path) -> Option<Vec<u8>>;
22+ fn get_current_head_name(&self, file: &Path) -> Option<Arc<ArcSwap<Box<str>>>>;
21}
23}
22
24
23#[doc(hidden)]
25#[doc(hidden)]
@@ -26,6 +28,10 @@ impl DiffProvider for Dummy {
26 fn get_diff_base(&self, _file: &Path) -> Option<Vec<u8>> {
28 fn get_diff_base(&self, _file: &Path) -> Option<Vec<u8>> {
27 None
29 None
28 }
30 }
31+
32+ fn get_current_head_name(&self, _file: &Path) -> Option<Arc<ArcSwap<Box<str>>>> {
33+ None
34+ }
29}
35}
30
36
31pub struct DiffProviderRegistry {
37pub struct DiffProviderRegistry {
@@ -38,6 +44,12 @@ impl DiffProviderRegistry {
38 .iter()
44 .iter()
39 .find_map(|provider| provider.get_diff_base(file))
45 .find_map(|provider| provider.get_diff_base(file))
40 }
46 }
47+
48+ pub fn get_current_head_name(&self, file: &Path) -> Option<Arc<ArcSwap<Box<str>>>> {
49+ self.providers
50+ .iter()
51+ .find_map(|provider| provider.get_current_head_name(file))
52+ }
41}
53}
42
54
43impl Default for DiffProviderRegistry {
55impl Default for DiffProviderRegistry {
helix-view/src/document.rs+16
@@ -1,5 +1,6 @@
1use anyhow::{anyhow, bail, Context, Error};
1use anyhow::{anyhow, bail, Context, Error};
2use arc_swap::access::DynAccess;
2use arc_swap::access::DynAccess;
3+use arc_swap::ArcSwap;
3use futures_util::future::BoxFuture;
4use futures_util::future::BoxFuture;
4use futures_util::FutureExt;
5use futures_util::FutureExt;
5use helix_core::auto_pairs::AutoPairs;
6use helix_core::auto_pairs::AutoPairs;
@@ -158,6 +159,7 @@ pub struct Document {
158 language_server: Option<Arc<helix_lsp::Client>>,
159 language_server: Option<Arc<helix_lsp::Client>>,
159
160
160 diff_handle: Option<DiffHandle>,
161 diff_handle: Option<DiffHandle>,
162+ version_control_head: Option<Arc<ArcSwap<Box<str>>>>,
161}
163}
162
164
163use std::{fmt, mem};
165use std::{fmt, mem};
@@ -404,6 +406,7 @@ impl Document {
404 language_server: None,
406 language_server: None,
405 diff_handle: None,
407 diff_handle: None,
406 config,
408 config,
409+ version_control_head: None,
407 }
410 }
408 }
411 }
409 pub fn default(config: Arc<dyn DynAccess<Config>>) -> Self {
412 pub fn default(config: Arc<dyn DynAccess<Config>>) -> Self {
@@ -707,6 +710,8 @@ impl Document {
707 None => self.diff_handle = None,
710 None => self.diff_handle = None,
708 }
711 }
709
712
713+ self.version_control_head = provider_registry.get_current_head_name(&path);
714+
710 Ok(())
715 Ok(())
711 }
716 }
712
717
@@ -1158,6 +1163,17 @@ impl Document {
1158 }
1163 }
1159 }
1164 }
1160
1165
1166+ pub fn version_control_head(&self) -> Option<Arc<Box<str>>> {
1167+ self.version_control_head.as_ref().map(|a| a.load_full())
1168+ }
1169+
1170+ pub fn set_version_control_head(
1171+ &mut self,
1172+ version_control_head: Option<Arc<ArcSwap<Box<str>>>>,
1173+ ) {
1174+ self.version_control_head = version_control_head;
1175+ }
1176+
1161 #[inline]
1177 #[inline]
1162 /// Tree-sitter AST tree
1178 /// Tree-sitter AST tree
1163 pub fn syntax(&self) -> Option<&Syntax> {
1179 pub fn syntax(&self) -> Option<&Syntax> {
helix-view/src/editor.rs+4
@@ -467,6 +467,9 @@ pub enum StatusLineElement {
467
467
468 /// A single space
468 /// A single space
469 Spacer,
469 Spacer,
470+
471+ /// Current version control information
472+ VersionControl,
470}
473}
471
474
472// Cursor shape is read and used on every rendered frame and so needs
475// Cursor shape is read and used on every rendered frame and so needs
@@ -1297,6 +1300,7 @@ impl Editor {
1297 if let Some(diff_base) = self.diff_providers.get_diff_base(&path) {
1300 if let Some(diff_base) = self.diff_providers.get_diff_base(&path) {
1298 doc.set_diff_base(diff_base, self.redraw_handle.clone());
1301 doc.set_diff_base(diff_base, self.redraw_handle.clone());
1299 }
1302 }
1303+ doc.set_version_control_head(self.diff_providers.get_current_head_name(&path));
1300
1304
1301 let id = self.new_document(doc);
1305 let id = self.new_document(doc);
1302 let _ = self.launch_language_server(id);
1306 let _ = self.launch_language_server(id);