Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Its settings will be merged with the configuration directory `config.toml` and t
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
| `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` |
| `diff-source` | The source to use for diff operations (gutter, `:diffg`, `]g`, etc). Can be `git`, `file` (uses the contents of the file on disk), or `none`. | `git` |

### `[editor.statusline]` Section

Expand Down
14 changes: 13 additions & 1 deletion helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use helix_lsp::{
use helix_view::{
align_view,
document::DocumentSavedEventResult,
editor::{ConfigEvent, EditorEvent},
editor::{ConfigEvent, DiffSource, EditorEvent},
graphics::Rect,
theme,
tree::Layout,
Expand Down Expand Up @@ -362,11 +362,15 @@ impl Application {
// the Application can apply it.
ConfigEvent::Update(editor_config) => {
let mut app_config = (*self.config.load().clone()).clone();
let update_diff_base = app_config.editor.diff_source != editor_config.diff_source;
app_config.editor = *editor_config;
if let Err(err) = self.terminal.reconfigure(app_config.editor.clone().into()) {
self.editor.set_error(err.to_string());
};
self.config.store(Arc::new(app_config));
if update_diff_base {
self.editor.update_diff_base();
}
}
}

Expand Down Expand Up @@ -568,6 +572,14 @@ impl Application {
self.editor.refresh_language_servers(id);
}

let diff_source = self.editor.config().diff_source;
let doc = doc_mut!(self.editor, &doc_save_event.doc_id);
if diff_source == DiffSource::File {
if let Some(path) = doc.path().cloned() {
doc.update_diff_base(&path, &self.editor.diff_provider, diff_source);
}
}

// TODO: fix being overwritten by lsp
self.editor.set_status(format!(
"'{}' written, {}L {}B",
Expand Down
11 changes: 7 additions & 4 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1283,10 +1283,12 @@ fn reload(
}

let scrolloff = cx.editor.config().scrolloff;
let diff_source = cx.editor.config().diff_source;
let (view, doc) = current!(cx.editor);
doc.reload(view, &cx.editor.diff_providers).map(|_| {
view.ensure_cursor_in_view(doc, scrolloff);
})?;
doc.reload(view, &cx.editor.diff_provider, diff_source)
.map(|_| {
view.ensure_cursor_in_view(doc, scrolloff);
})?;
if let Some(path) = doc.path() {
cx.editor
.language_servers
Expand Down Expand Up @@ -1324,6 +1326,7 @@ fn reload_all(
.collect();

for (doc_id, view_ids) in docs_view_ids {
let diff_source = cx.editor.config().diff_source;
let doc = doc_mut!(cx.editor, &doc_id);

// Every doc is guaranteed to have at least 1 view at this point.
Expand All @@ -1332,7 +1335,7 @@ fn reload_all(
// Ensure that the view is synced with the document's history.
view.sync_changes(doc);

doc.reload(view, &cx.editor.diff_providers)?;
doc.reload(view, &cx.editor.diff_provider, diff_source)?;
if let Some(path) = doc.path() {
cx.editor
.language_servers
Expand Down
8 changes: 2 additions & 6 deletions helix-vcs/src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ use gix::objs::tree::EntryMode;
use gix::sec::trust::DefaultForLevel;
use gix::{Commit, ObjectId, Repository, ThreadSafeRepository};

use crate::DiffProvider;

#[cfg(test)]
mod test;

Expand Down Expand Up @@ -59,10 +57,8 @@ impl Git {

Ok(res)
}
}

impl DiffProvider for Git {
fn get_diff_base(&self, file: &Path) -> Result<Vec<u8>> {
pub fn get_diff_base(&self, file: &Path) -> Result<Vec<u8>> {
debug_assert!(!file.exists() || file.is_file());
debug_assert!(file.is_absolute());

Expand Down Expand Up @@ -101,7 +97,7 @@ impl DiffProvider for Git {
Ok(data)
}

fn get_current_head_name(&self, file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
pub fn get_current_head_name(&self, file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
debug_assert!(!file.exists() || file.is_file());
debug_assert!(file.is_absolute());
let repo_dir = file.parent().context("file has no parent directory")?;
Expand Down
2 changes: 1 addition & 1 deletion helix-vcs/src/git/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{fs::File, io::Write, path::Path, process::Command};

use tempfile::TempDir;

use crate::{DiffProvider, Git};
use crate::Git;

fn exec_git_cmd(args: &str, git_dir: &Path) {
let res = Command::new("git")
Expand Down
57 changes: 3 additions & 54 deletions helix-vcs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,65 +14,14 @@ mod diff;

pub use diff::{DiffHandle, Hunk};

pub trait DiffProvider {
/// Returns the data that a diff should be computed against
/// if this provider is used.
/// The data is returned as raw byte without any decoding or encoding performed
/// to ensure all file encodings are handled correctly.
fn get_diff_base(&self, file: &Path) -> Result<Vec<u8>>;
fn get_current_head_name(&self, file: &Path) -> Result<Arc<ArcSwap<Box<str>>>>;
}

#[doc(hidden)]
pub struct Dummy;
impl DiffProvider for Dummy {
fn get_diff_base(&self, _file: &Path) -> Result<Vec<u8>> {
impl Dummy {
pub fn get_diff_base(&self, _file: &Path) -> Result<Vec<u8>> {
bail!("helix was compiled without git support")
}

fn get_current_head_name(&self, _file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
pub fn get_current_head_name(&self, _file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
bail!("helix was compiled without git support")
}
}

pub struct DiffProviderRegistry {
providers: Vec<Box<dyn DiffProvider>>,
}

impl DiffProviderRegistry {
pub fn get_diff_base(&self, file: &Path) -> Option<Vec<u8>> {
self.providers
.iter()
.find_map(|provider| match provider.get_diff_base(file) {
Ok(res) => Some(res),
Err(err) => {
log::info!("{err:#?}");
log::info!("failed to open diff base for {}", file.display());
None
}
})
}

pub fn get_current_head_name(&self, file: &Path) -> Option<Arc<ArcSwap<Box<str>>>> {
self.providers
.iter()
.find_map(|provider| match provider.get_current_head_name(file) {
Ok(res) => Some(res),
Err(err) => {
log::info!("{err:#?}");
log::info!("failed to obtain current head name for {}", file.display());
None
}
})
}
}

impl Default for DiffProviderRegistry {
fn default() -> Self {
// currently only git is supported
// TODO make this configurable when more providers are added
let git: Box<dyn DiffProvider> = Box::new(Git);
let providers = vec![git];
DiffProviderRegistry { providers }
}
}
76 changes: 54 additions & 22 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use helix_core::doc_formatter::TextFormat;
use helix_core::encoding::Encoding;
use helix_core::syntax::{Highlight, LanguageServerFeature};
use helix_core::text_annotations::{InlineAnnotation, TextAnnotations};
use helix_vcs::{DiffHandle, DiffProviderRegistry};
use helix_vcs::{DiffHandle, Git};

use ::parking_lot::Mutex;
use serde::de::{self, Deserialize, Deserializer};
Expand All @@ -33,7 +33,7 @@ use helix_core::{
ChangeSet, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction,
};

use crate::editor::Config;
use crate::editor::{Config, DiffSource};
use crate::{DocumentId, Editor, Theme, View, ViewId};

/// 8kB of buffer space for encoding and decoding `Rope`s.
Expand Down Expand Up @@ -994,7 +994,8 @@ impl Document {
pub fn reload(
&mut self,
view: &mut View,
provider_registry: &DiffProviderRegistry,
diff_provider: &Git,
diff_source: DiffSource,
) -> Result<(), Error> {
let encoding = self.encoding;
let path = self
Expand All @@ -1021,12 +1022,7 @@ impl Document {

self.detect_indent_and_line_ending();

match provider_registry.get_diff_base(&path) {
Some(diff_base) => self.set_diff_base(diff_base),
None => self.diff_handle = None,
}

self.version_control_head = provider_registry.get_current_head_name(&path);
self.update_diff_base(&path, diff_provider, diff_source);

Ok(())
}
Expand Down Expand Up @@ -1582,27 +1578,63 @@ impl Document {
}

/// Intialize/updates the differ for this document with a new base.
pub fn set_diff_base(&mut self, diff_base: Vec<u8>) {
fn set_diff_base_bytes(&mut self, diff_base: Vec<u8>) {
if let Ok((diff_base, ..)) = from_reader(&mut diff_base.as_slice(), Some(self.encoding)) {
if let Some(differ) = &self.diff_handle {
differ.update_diff_base(diff_base);
return;
}
self.diff_handle = Some(DiffHandle::new(diff_base, self.text.clone()))
self.set_diff_base(diff_base);
} else {
self.diff_handle = None;
}
}

pub fn version_control_head(&self) -> Option<Arc<Box<str>>> {
self.version_control_head.as_ref().map(|a| a.load_full())
fn set_diff_base(&mut self, diff_base: Rope) {
if let Some(differ) = &self.diff_handle {
differ.update_diff_base(diff_base);
return;
}
self.diff_handle = Some(DiffHandle::new(diff_base, self.text.clone()))
}

pub fn set_version_control_head(
&mut self,
version_control_head: Option<Arc<ArcSwap<Box<str>>>>,
) {
self.version_control_head = version_control_head;
pub fn update_diff_base(&mut self, path: &Path, diff_provider: &Git, diff_source: DiffSource) {
match diff_source {
DiffSource::Git => match diff_provider.get_diff_base(path) {
Ok(diff_base) => self.set_diff_base_bytes(diff_base),
Err(err) => {
log::info!("{err:#?}");
log::info!("failed to open diff base for for {}", path.display());
self.diff_handle = None;
}
},
DiffSource::File => {
if self.is_modified() {
match std::fs::read(path) {
Ok(bytes) => self.set_diff_base_bytes(bytes),
Err(err) => {
log::info!("{err:#?}");
log::info!("failed to open diff base for for {}", path.display());
self.diff_handle = None;
}
}
} else {
self.set_diff_base(self.text.clone());
}
}
DiffSource::None => {
self.diff_handle = None;
}
}

self.version_control_head = match diff_provider.get_current_head_name(path) {
Ok(res) => Some(res),
Err(err) => {
log::info!("{err:#?}");
log::info!("failed to obtain current head name for {}", path.display());
None
}
};
}

pub fn version_control_head(&self) -> Option<Arc<Box<str>>> {
self.version_control_head.as_ref().map(|a| a.load_full())
}

#[inline]
Expand Down
Loading